MessageMain()   F
last analyzed

Complexity

Conditions 28
Paths > 20000

Size

Total Lines 195
Code Lines 95

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 95
nc 589824
nop 0
dl 0
loc 195
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file 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 2022 Simple Machines and individual contributors
13
 * @license https://www.simplemachines.org/about/smf/license.php BSD
14
 *
15
 * @version 2.1.0
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
introduced by
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)
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise & or did you mean &&?
Loading history...
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
		// Execute the query!
919
		$messages_request = $smcFunc['db_query']('', '
920
			SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
921
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '
922
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') . ($context['sort_by'] == 'name' ? '
923
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})' : '') . '
924
			WHERE pm.id_pm IN ({array_int:display_pms})' . ($context['folder'] == 'sent' ? '
925
			GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' : '') . '
926
			ORDER BY ' . ($context['display_mode'] == 2 ? 'pm.id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . '
927
			LIMIT {int:limit}',
928
			array(
929
				'display_pms' => $display_pms,
930
				'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
931
				'limit' => count($display_pms),
932
				'sort' => $_GET['sort'],
933
			)
934
		);
935
936
		// Build the conversation button array.
937
		if ($context['display_mode'] == 2)
938
		{
939
			$context['conversation_buttons'] = array(
940
				'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'),
941
			);
942
943
			// Allow mods to add additional buttons here
944
			call_integration_hook('integrate_conversation_buttons');
945
		}
946
	}
947
	else
948
		$messages_request = false;
949
950
	$context['can_send_pm'] = allowedTo('pm_send');
951
	$context['can_send_email'] = allowedTo('moderate_forum');
952
	$context['sub_template'] = 'folder';
953
	$context['page_title'] = $txt['pm_inbox'];
954
955
	// Finally mark the relevant messages as read.
956
	if ($context['folder'] != 'sent' && !empty($context['labels'][(int) $context['current_label_id']]['unread_messages']))
957
	{
958
		// If the display mode is "old sk00l" do them all...
959
		if ($context['display_mode'] == 0)
960
			markMessages(null, $context['current_label_id']);
961
		// Otherwise do just the current one!
962
		elseif (!empty($context['current_pm']))
963
			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...
964
	}
965
}
966
967
/**
968
 * Get a personal message for the theme.  (used to save memory.)
969
 *
970
 * @param string $type The type of message
971
 * @param bool $reset Whether to reset the internal pointer
972
 * @return bool|array False on failure, otherwise an array of info
973
 */
974
function prepareMessageContext($type = 'subject', $reset = false)
975
{
976
	global $txt, $scripturl, $modSettings, $context, $messages_request, $memberContext, $recipients, $smcFunc;
977
	global $user_info, $subjects_request;
978
979
	// Count the current message number....
980
	static $counter = null;
981
	if ($counter === null || $reset)
982
		$counter = $context['start'];
983
984
	static $temp_pm_selected = null;
985
	if ($temp_pm_selected === null)
986
	{
987
		$temp_pm_selected = isset($_SESSION['pm_selected']) ? $_SESSION['pm_selected'] : array();
988
		$_SESSION['pm_selected'] = array();
989
	}
990
991
	// If we're in non-boring view do something exciting!
992
	if ($context['display_mode'] != 0 && $subjects_request && $type == 'subject')
993
	{
994
		$subject = $smcFunc['db_fetch_assoc']($subjects_request);
995
		if (!$subject)
996
		{
997
			$smcFunc['db_free_result']($subjects_request);
998
			return false;
999
		}
1000
1001
		$subject['subject'] = $subject['subject'] == '' ? $txt['no_subject'] : $subject['subject'];
1002
		censorText($subject['subject']);
1003
1004
		$output = array(
1005
			'id' => $subject['id_pm'],
1006
			'member' => array(
1007
				'id' => $subject['id_member_from'],
1008
				'name' => $subject['from_name'],
1009
				'link' => ($subject['id_member_from'] != 0) ? '<a href="' . $scripturl . '?action=profile;u=' . $subject['id_member_from'] . '">' . $subject['from_name'] . '</a>' : $subject['from_name'],
1010
			),
1011
			'recipients' => &$recipients[$subject['id_pm']],
1012
			'subject' => $subject['subject'],
1013
			'time' => timeformat($subject['msgtime']),
1014
			'timestamp' => $subject['msgtime'],
1015
			'number_recipients' => count($recipients[$subject['id_pm']]['to']),
1016
			'labels' => &$context['message_labels'][$subject['id_pm']],
1017
			'fully_labeled' => count(isset($context['message_labels'][$subject['id_pm']]) ? $context['message_labels'][$subject['id_pm']] : array()) == count($context['labels']),
1018
			'is_replied_to' => &$context['message_replied'][$subject['id_pm']],
1019
			'is_unread' => &$context['message_unread'][$subject['id_pm']],
1020
			'is_selected' => !empty($temp_pm_selected) && in_array($subject['id_pm'], $temp_pm_selected),
1021
		);
1022
1023
		return $output;
1024
	}
1025
1026
	// Bail if it's false, ie. no messages.
1027
	if ($messages_request == false)
1028
		return false;
1029
1030
	// Reset the data?
1031
	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...
1032
		return @$smcFunc['db_data_seek']($messages_request, 0);
1033
1034
	// Get the next one... bail if anything goes wrong.
1035
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1036
	if (!$message)
1037
	{
1038
		if ($type != 'subject')
1039
			$smcFunc['db_free_result']($messages_request);
1040
1041
		return false;
1042
	}
1043
1044
	// Use '(no subject)' if none was specified.
1045
	$message['subject'] = $message['subject'] == '' ? $txt['no_subject'] : $message['subject'];
1046
1047
	// Load the message's information - if it's not there, load the guest information.
1048
	if (!loadMemberContext($message['id_member_from'], true))
1049
	{
1050
		$memberContext[$message['id_member_from']]['name'] = $message['from_name'];
1051
		$memberContext[$message['id_member_from']]['id'] = 0;
1052
1053
		// Sometimes the forum sends messages itself (Warnings are an example) - in this case don't label it from a guest.
1054
		$memberContext[$message['id_member_from']]['group'] = $message['from_name'] == $context['forum_name_html_safe'] ? '' : $txt['guest_title'];
1055
		$memberContext[$message['id_member_from']]['link'] = $message['from_name'];
1056
		$memberContext[$message['id_member_from']]['email'] = '';
1057
		$memberContext[$message['id_member_from']]['show_email'] = false;
1058
		$memberContext[$message['id_member_from']]['is_guest'] = true;
1059
	}
1060
	else
1061
	{
1062
		$memberContext[$message['id_member_from']]['can_view_profile'] = allowedTo('profile_view') || ($message['id_member_from'] == $user_info['id'] && !$user_info['is_guest']);
1063
		$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'])));
1064
		// Show the email if it's your own PM
1065
		$memberContext[$message['id_member_from']]['show_email'] |= $message['id_member_from'] == $user_info['id'];
1066
	}
1067
1068
	$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']);
1069
1070
	// Censor all the important text...
1071
	censorText($message['body']);
1072
	censorText($message['subject']);
1073
1074
	// Run UBBC interpreter on the message.
1075
	$message['body'] = parse_bbc($message['body'], true, 'pm' . $message['id_pm']);
1076
1077
	// Send the array.
1078
	$output = array(
1079
		'id' => $message['id_pm'],
1080
		'member' => &$memberContext[$message['id_member_from']],
1081
		'subject' => $message['subject'],
1082
		'time' => timeformat($message['msgtime']),
1083
		'timestamp' => $message['msgtime'],
1084
		'counter' => $counter,
1085
		'body' => $message['body'],
1086
		'recipients' => &$recipients[$message['id_pm']],
1087
		'number_recipients' => count($recipients[$message['id_pm']]['to']),
1088
		'labels' => &$context['message_labels'][$message['id_pm']],
1089
		'fully_labeled' => count(isset($context['message_labels'][$message['id_pm']]) ? $context['message_labels'][$message['id_pm']] : array()) == count($context['labels']),
1090
		'is_replied_to' => &$context['message_replied'][$message['id_pm']],
1091
		'is_unread' => &$context['message_unread'][$message['id_pm']],
1092
		'is_selected' => !empty($temp_pm_selected) && in_array($message['id_pm'], $temp_pm_selected),
1093
		'is_message_author' => $message['id_member_from'] == $user_info['id'],
1094
		'can_report' => !empty($modSettings['enableReportPM']),
1095
		'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member_from'] == $user_info['id'] && !empty($user_info['id'])),
1096
	);
1097
1098
	$counter++;
1099
1100
	// Any custom profile fields?
1101
	if (!empty($memberContext[$message['id_member_from']]['custom_fields']))
1102
		foreach ($memberContext[$message['id_member_from']]['custom_fields'] as $custom)
1103
			$output['custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
1104
1105
	$output['quickbuttons'] = array(
1106
		'reply_to_all' => array(
1107
			'label' => $txt['reply_to_all'],
1108
			'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',
1109
			'icon' => 'reply_all_button',
1110
			'show' => $context['can_send_pm'] && !$output['member']['is_guest'] && ($output['number_recipients'] > 1 || $output['member']['id'] == $user_info['id']),
1111
		),
1112
		'reply' => array(
1113
			'label' => $txt['reply'],
1114
			'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'],
1115
			'icon' => 'reply_button',
1116
			'show' => $context['can_send_pm'] && !$output['member']['is_guest'] && $output['member']['id'] != $user_info['id'],
1117
		),
1118
		'quote' => array(
1119
			'label' => $txt['quote_action'],
1120
			'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'] : '')),
1121
			'icon' => 'quote',
1122
			'show' => $context['can_send_pm'],
1123
		),
1124
		'delete' => array(
1125
			'label' => $txt['delete'],
1126
			'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'],
1127
			'javascript' => 'data-confirm="' . JavaScriptEscape($txt['remove_message_question']) . '"',
1128
			'class' => 'you_sure',
1129
			'icon' => 'remove_button',
1130
		),
1131
		'more' => array(
1132
			'report' => array(
1133
				'label' => $txt['pm_report_to_admin'],
1134
				'href' => $scripturl . '?action=pm;sa=report;l=' . $context['current_label_id'] . ';pmsg=' . $output['id'],
1135
				'icon' => 'error',
1136
				'show' => $output['can_report']
1137
			),
1138
		),
1139
		'quickmod' => array(
1140
			'class' => 'inline_mod_check',
1141
			'content' => '<input type="checkbox" name="pms[]" id="deletedisplay' . $output['id'] . '" value="' . $output['id'] . '" onclick="document.getElementById(\'deletelisting' . $output['id'] . '\').checked = this.checked;">',
1142
			'show' => empty($context['display_mode'])
1143
		)
1144
	);
1145
1146
	call_integration_hook('integrate_prepare_pm_context', array(&$output, &$message, $counter));
1147
1148
	return $output;
1149
}
1150
1151
/**
1152
 * Allows searching through personal messages.
1153
 */
1154
function MessageSearch()
1155
{
1156
	global $context, $txt, $scripturl, $smcFunc;
1157
1158
	if (isset($_REQUEST['params']))
1159
	{
1160
		$temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
1161
		$context['search_params'] = array();
1162
		foreach ($temp_params as $i => $data)
1163
		{
1164
			@list ($k, $v) = explode('|\'|', $data);
1165
			$context['search_params'][$k] = $v;
1166
		}
1167
	}
1168
	if (isset($_REQUEST['search']))
1169
		$context['search_params']['search'] = un_htmlspecialchars($_REQUEST['search']);
1170
1171
	if (isset($context['search_params']['search']))
1172
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
1173
	if (isset($context['search_params']['userspec']))
1174
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
1175
1176
	if (!empty($context['search_params']['searchtype']))
1177
		$context['search_params']['searchtype'] = 2;
1178
1179
	if (!empty($context['search_params']['minage']))
1180
		$context['search_params']['minage'] = (int) $context['search_params']['minage'];
1181
1182
	if (!empty($context['search_params']['maxage']))
1183
		$context['search_params']['maxage'] = (int) $context['search_params']['maxage'];
1184
1185
	$context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']);
1186
	$context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']);
1187
1188
	// Create the array of labels to be searched.
1189
	$context['search_labels'] = array();
1190
	$searchedLabels = isset($context['search_params']['labels']) && $context['search_params']['labels'] != '' ? explode(',', $context['search_params']['labels']) : array();
1191
	foreach ($context['labels'] as $label)
1192
	{
1193
		$context['search_labels'][] = array(
1194
			'id' => $label['id'],
1195
			'name' => $label['name'],
1196
			'checked' => !empty($searchedLabels) ? in_array($label['id'], $searchedLabels) : true,
1197
		);
1198
	}
1199
1200
	// Are all the labels checked?
1201
	$context['check_all'] = empty($searchedLabels) || count($context['search_labels']) == count($searchedLabels);
1202
1203
	// Load the error text strings if there were errors in the search.
1204
	if (!empty($context['search_errors']))
1205
	{
1206
		loadLanguage('Errors');
1207
		$context['search_errors']['messages'] = array();
1208
		foreach ($context['search_errors'] as $search_error => $dummy)
1209
		{
1210
			if ($search_error == 'messages')
1211
				continue;
1212
1213
			$context['search_errors']['messages'][] = $txt['error_' . $search_error];
1214
		}
1215
	}
1216
1217
	$context['page_title'] = $txt['pm_search_title'];
1218
	$context['sub_template'] = 'search';
1219
	$context['linktree'][] = array(
1220
		'url' => $scripturl . '?action=pm;sa=search',
1221
		'name' => $txt['pm_search_bar_title'],
1222
	);
1223
}
1224
1225
/**
1226
 * Actually do the search of personal messages.
1227
 */
1228
function MessageSearch2()
1229
{
1230
	global $scripturl, $modSettings, $user_info, $context, $txt;
1231
	global $memberContext, $smcFunc;
1232
1233
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search'])
1234
		fatal_lang_error('loadavg_search_disabled', false);
1235
1236
	/**
1237
	 * @todo For the moment force the folder to the inbox.
1238
	 * @todo Maybe set the inbox based on a cookie or theme setting?
1239
	 */
1240
	$context['folder'] = 'inbox';
1241
1242
	// Some useful general permissions.
1243
	$context['can_send_pm'] = allowedTo('pm_send');
1244
1245
	// Some hardcoded veriables that can be tweaked if required.
1246
	$maxMembersToSearch = 500;
1247
1248
	// Extract all the search parameters.
1249
	$search_params = array();
1250
	if (isset($_REQUEST['params']))
1251
	{
1252
		$temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
1253
		foreach ($temp_params as $i => $data)
1254
		{
1255
			@list ($k, $v) = explode('|\'|', $data);
1256
			$search_params[$k] = $v;
1257
		}
1258
	}
1259
1260
	$context['start'] = isset($_GET['start']) ? (int) $_GET['start'] : 0;
1261
1262
	// Store whether simple search was used (needed if the user wants to do another query).
1263
	if (!isset($search_params['advanced']))
1264
		$search_params['advanced'] = empty($_REQUEST['advanced']) ? 0 : 1;
1265
1266
	// 1 => 'allwords' (default, don't set as param) / 2 => 'anywords'.
1267
	if (!empty($search_params['searchtype']) || (!empty($_REQUEST['searchtype']) && $_REQUEST['searchtype'] == 2))
1268
		$search_params['searchtype'] = 2;
1269
1270
	// Minimum age of messages. Default to zero (don't set param in that case).
1271
	if (!empty($search_params['minage']) || (!empty($_REQUEST['minage']) && $_REQUEST['minage'] > 0))
1272
		$search_params['minage'] = !empty($search_params['minage']) ? (int) $search_params['minage'] : (int) $_REQUEST['minage'];
1273
1274
	// Maximum age of messages. Default to infinite (9999 days: param not set).
1275
	if (!empty($search_params['maxage']) || (!empty($_REQUEST['maxage']) && $_REQUEST['maxage'] != 9999))
1276
		$search_params['maxage'] = !empty($search_params['maxage']) ? (int) $search_params['maxage'] : (int) $_REQUEST['maxage'];
1277
1278
	$search_params['subject_only'] = !empty($search_params['subject_only']) || !empty($_REQUEST['subject_only']);
1279
	$search_params['show_complete'] = !empty($search_params['show_complete']) || !empty($_REQUEST['show_complete']);
1280
1281
	// Default the user name to a wildcard matching every user (*).
1282
	if (!empty($search_params['user_spec']) || (!empty($_REQUEST['userspec']) && $_REQUEST['userspec'] != '*'))
1283
		$search_params['userspec'] = isset($search_params['userspec']) ? $search_params['userspec'] : $_REQUEST['userspec'];
1284
1285
	// This will be full of all kinds of parameters!
1286
	$searchq_parameters = array();
1287
1288
	// If there's no specific user, then don't mention it in the main query.
1289
	if (empty($search_params['userspec']))
1290
		$userQuery = '';
1291
	else
1292
	{
1293
		$userString = strtr($smcFunc['htmlspecialchars']($search_params['userspec'], ENT_QUOTES), array('&quot;' => '"'));
1294
		$userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_'));
1295
1296
		preg_match_all('~"([^"]+)"~', $userString, $matches);
1297
		$possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString)));
1298
1299
		for ($k = 0, $n = count($possible_users); $k < $n; $k++)
1300
		{
1301
			$possible_users[$k] = trim($possible_users[$k]);
1302
1303
			if (strlen($possible_users[$k]) == 0)
1304
				unset($possible_users[$k]);
1305
		}
1306
1307
		if (!empty($possible_users))
1308
		{
1309
			// We need to bring this into the query and do it nice and cleanly.
1310
			$where_params = array();
1311
			$where_clause = array();
1312
			foreach ($possible_users as $k => $v)
1313
			{
1314
				$where_params['name_' . $k] = $v;
1315
				$where_clause[] = '{raw:real_name} LIKE {string:name_' . $k . '}';
1316
				if (!isset($where_params['real_name']))
1317
					$where_params['real_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name';
1318
			}
1319
1320
			// Who matches those criteria?
1321
			// @todo This doesn't support sent item searching.
1322
			$request = $smcFunc['db_query']('', '
1323
				SELECT id_member
1324
				FROM {db_prefix}members
1325
				WHERE ' . implode(' OR ', $where_clause),
1326
				$where_params
1327
			);
1328
1329
			// Simply do nothing if there're too many members matching the criteria.
1330
			if ($smcFunc['db_num_rows']($request) > $maxMembersToSearch)
1331
				$userQuery = '';
1332
			elseif ($smcFunc['db_num_rows']($request) == 0)
1333
			{
1334
				$userQuery = 'AND pm.id_member_from = 0 AND ({raw:pm_from_name} LIKE {raw:guest_user_name_implode})';
1335
				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
1336
				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
1337
			}
1338
			else
1339
			{
1340
				$memberlist = array();
1341
				while ($row = $smcFunc['db_fetch_assoc']($request))
1342
					$memberlist[] = $row['id_member'];
1343
				$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})))';
1344
				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
1345
				$searchq_parameters['member_list'] = $memberlist;
1346
				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
1347
			}
1348
			$smcFunc['db_free_result']($request);
1349
		}
1350
		else
1351
			$userQuery = '';
1352
	}
1353
1354
	// Setup the sorting variables...
1355
	// @todo Add more in here!
1356
	$sort_columns = array(
1357
		'pm.id_pm',
1358
	);
1359
	if (empty($search_params['sort']) && !empty($_REQUEST['sort']))
1360
		list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, '');
1361
	$search_params['sort'] = !empty($search_params['sort']) && in_array($search_params['sort'], $sort_columns) ? $search_params['sort'] : 'pm.id_pm';
1362
	$search_params['sort_dir'] = !empty($search_params['sort_dir']) && $search_params['sort_dir'] == 'asc' ? 'asc' : 'desc';
1363
1364
	// Sort out any labels we may be searching by.
1365
	$context['search_in'] = array();
1366
	$labelQuery = '';
1367
	$labelJoin = '';
1368
	if ($context['folder'] == 'inbox' && !empty($search_params['advanced']) && $context['currently_using_labels'])
1369
	{
1370
		// Came here from pagination?  Put them back into $_REQUEST for sanitization.
1371
		if (isset($search_params['labels']))
1372
			$_REQUEST['searchlabel'] = explode(',', $search_params['labels']);
1373
1374
		// Assuming we have some labels - make them all integers.
1375
		if (!empty($_REQUEST['searchlabel']) && is_array($_REQUEST['searchlabel']))
1376
		{
1377
			foreach ($_REQUEST['searchlabel'] as $key => $id)
1378
				$_REQUEST['searchlabel'][$key] = (int) $id;
1379
		}
1380
		else
1381
			$_REQUEST['searchlabel'] = array();
1382
1383
		// Now that everything is cleaned up a bit, make the labels a param.
1384
		$search_params['labels'] = implode(',', $_REQUEST['searchlabel']);
1385
1386
		// No labels selected? That must be an error!
1387
		if (empty($_REQUEST['searchlabel']))
1388
			$context['search_errors']['no_labels_selected'] = true;
1389
		// Otherwise prepare the query!
1390
		elseif (count($_REQUEST['searchlabel']) != count($context['labels']))
1391
		{
1392
			// Special case here... "inbox" isn't a real label anymore...
1393
			if (in_array(-1, $_REQUEST['searchlabel']))
1394
			{
1395
				$context['search_in'][] = $context['labels'][-1]['name'];
1396
1397
				$labelQuery = '	AND pmr.in_inbox = {int:in_inbox}';
1398
				$searchq_parameters['in_inbox'] = 1;
1399
1400
				// Now we get rid of that...
1401
				$temp = array_diff($_REQUEST['searchlabel'], array(-1));
1402
				$_REQUEST['searchlabel'] = $temp;
1403
			}
1404
1405
			// Still have something?
1406
			if (!empty($_REQUEST['searchlabel']))
1407
			{
1408
				if ($labelQuery == '')
1409
				{
1410
					// Not searching the inbox - PM must be labeled
1411
					$labelQuery = ' AND pml.id_label IN ({array_int:labels})';
1412
					$labelJoin = ' INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1413
				}
1414
				else
1415
				{
1416
					// Searching the inbox - PM doesn't have to be labeled
1417
					$labelQuery = ' AND (' . substr($labelQuery, 5) . ' OR pml.id_label IN ({array_int:labels}))';
1418
					$labelJoin = ' LEFT JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1419
				}
1420
1421
				$searchq_parameters['labels'] = $_REQUEST['searchlabel'];
1422
1423
				foreach ($_REQUEST['searchlabel'] as $label_key)
1424
					$context['search_in'][] = $context['labels'][$label_key]['name'];
1425
			}
1426
		}
1427
	}
1428
1429
	if (empty($context['search_in']))
1430
		$context['search_in'][] = $context['folder'];
1431
1432
	// What are we actually searching for?
1433
	$search_params['search'] = !empty($search_params['search']) ? $search_params['search'] : (isset($_REQUEST['search']) ? $_REQUEST['search'] : '');
1434
	// If we ain't got nothing - we should error!
1435
	if (!isset($search_params['search']) || $search_params['search'] == '')
1436
		$context['search_errors']['invalid_search_string'] = true;
1437
1438
	// Extract phrase parts first (e.g. some words "this is a phrase" some more words.)
1439
	preg_match_all('~(?:^|\s)([-]?)"([^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), $search_params['search'], $matches, PREG_PATTERN_ORDER);
1440
	$searchArray = $matches[2];
1441
1442
	// Remove the phrase parts and extract the words.
1443
	$tempSearch = explode(' ', preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']));
1444
1445
	// A minus sign in front of a word excludes the word.... so...
1446
	$excludedWords = array();
1447
1448
	// .. first, we check for things like -"some words", but not "-some words".
1449
	foreach ($matches[1] as $index => $word)
1450
		if ($word == '-')
1451
		{
1452
			$word = $smcFunc['strtolower'](trim($searchArray[$index]));
1453
			if (strlen($word) > 0)
1454
				$excludedWords[] = $word;
1455
			unset($searchArray[$index]);
1456
		}
1457
1458
	// Now we look for -test, etc.... normaller.
1459
	foreach ($tempSearch as $index => $word)
1460
	{
1461
		if (strpos(trim($word), '-') === 0)
1462
		{
1463
			$word = substr($smcFunc['strtolower']($word), 1);
1464
			if (strlen($word) > 0)
1465
				$excludedWords[] = $word;
1466
			unset($tempSearch[$index]);
1467
		}
1468
	}
1469
1470
	$searchArray = array_merge($searchArray, $tempSearch);
1471
1472
	// Trim everything and make sure there are no words that are the same.
1473
	foreach ($searchArray as $index => $value)
1474
	{
1475
		$searchArray[$index] = $smcFunc['strtolower'](trim($value));
1476
		if ($searchArray[$index] == '')
1477
			unset($searchArray[$index]);
1478
		else
1479
		{
1480
			// Sort out entities first.
1481
			$searchArray[$index] = $smcFunc['htmlspecialchars']($searchArray[$index]);
1482
		}
1483
	}
1484
	$searchArray = array_unique($searchArray);
1485
1486
	// Create an array of replacements for highlighting.
1487
	$context['mark'] = array();
1488
	foreach ($searchArray as $word)
1489
		$context['mark'][$word] = '<strong class="highlight">' . $word . '</strong>';
1490
1491
	// This contains *everything*
1492
	$searchWords = array_merge($searchArray, $excludedWords);
1493
1494
	// Make sure at least one word is being searched for.
1495
	if (empty($searchArray))
1496
		$context['search_errors']['invalid_search_string'] = true;
1497
1498
	// Sort out the search query so the user can edit it - if they want.
1499
	$context['search_params'] = $search_params;
1500
	if (isset($context['search_params']['search']))
1501
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
1502
	if (isset($context['search_params']['userspec']))
1503
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
1504
1505
	// Now we have all the parameters, combine them together for pagination and the like...
1506
	$context['params'] = array();
1507
	foreach ($search_params as $k => $v)
1508
		$context['params'][] = $k . '|\'|' . $v;
1509
	$context['params'] = base64_encode(implode('|"|', $context['params']));
1510
1511
	// Compile the subject query part.
1512
	$andQueryParts = array();
1513
1514
	foreach ($searchWords as $index => $word)
1515
	{
1516
		if ($word == '')
1517
			continue;
1518
1519
		if ($search_params['subject_only'])
1520
			$andQueryParts[] = 'pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '}';
1521
		else
1522
			$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 . '})';
1523
		$searchq_parameters['search_' . $index] = '%' . strtr($word, array('_' => '\\_', '%' => '\\%')) . '%';
1524
	}
1525
1526
	$searchQuery = ' 1=1';
1527
	if (!empty($andQueryParts))
1528
		$searchQuery = implode(!empty($search_params['searchtype']) && $search_params['searchtype'] == 2 ? ' OR ' : ' AND ', $andQueryParts);
1529
1530
	// Age limits?
1531
	$timeQuery = '';
1532
	if (!empty($search_params['minage']))
1533
		$timeQuery .= ' AND pm.msgtime < ' . (time() - $search_params['minage'] * 86400);
1534
	if (!empty($search_params['maxage']))
1535
		$timeQuery .= ' AND pm.msgtime > ' . (time() - $search_params['maxage'] * 86400);
1536
1537
	// If we have errors - return back to the first screen...
1538
	if (!empty($context['search_errors']))
1539
	{
1540
		$_REQUEST['params'] = $context['params'];
1541
		return MessageSearch();
0 ignored issues
show
Bug introduced by
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...
1542
	}
1543
1544
	// Get the amount of results.
1545
	$request = $smcFunc['db_query']('', '
1546
		SELECT COUNT(*)
1547
		FROM {db_prefix}pm_recipients AS pmr
1548
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1549
			' . $labelJoin . '
1550
		WHERE ' . ($context['folder'] == 'inbox' ? '
1551
			pmr.id_member = {int:current_member}
1552
			AND pmr.deleted = {int:not_deleted}' : '
1553
			pm.id_member_from = {int:current_member}
1554
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1555
			' . $userQuery . $labelQuery . $timeQuery . '
1556
			AND (' . $searchQuery . ')',
1557
		array_merge($searchq_parameters, array(
1558
			'current_member' => $user_info['id'],
1559
			'not_deleted' => 0,
1560
		))
1561
	);
1562
	list ($numResults) = $smcFunc['db_fetch_row']($request);
1563
	$smcFunc['db_free_result']($request);
1564
1565
	// Get all the matching messages... using standard search only (No caching and the like!)
1566
	// @todo This doesn't support sent item searching yet.
1567
	$request = $smcFunc['db_query']('', '
1568
		SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
1569
		FROM {db_prefix}pm_recipients AS pmr
1570
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1571
			' . $labelJoin . '
1572
		WHERE ' . ($context['folder'] == 'inbox' ? '
1573
			pmr.id_member = {int:current_member}
1574
			AND pmr.deleted = {int:not_deleted}' : '
1575
			pm.id_member_from = {int:current_member}
1576
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1577
			' . $userQuery . $labelQuery . $timeQuery . '
1578
			AND (' . $searchQuery . ')
1579
		ORDER BY {raw:sort} {raw:sort_dir}
1580
		LIMIT {int:start}, {int:max}',
1581
		array_merge($searchq_parameters, array(
1582
			'current_member' => $user_info['id'],
1583
			'not_deleted' => 0,
1584
			'sort' => $search_params['sort'],
1585
			'sort_dir' => $search_params['sort_dir'],
1586
			'start' => $context['start'],
1587
			'max' => $modSettings['search_results_per_page'],
1588
		))
1589
	);
1590
	$foundMessages = array();
1591
	$posters = array();
1592
	$head_pms = array();
1593
	while ($row = $smcFunc['db_fetch_assoc']($request))
1594
	{
1595
		$foundMessages[] = $row['id_pm'];
1596
		$posters[] = $row['id_member_from'];
1597
		$head_pms[$row['id_pm']] = $row['id_pm_head'];
1598
	}
1599
	$smcFunc['db_free_result']($request);
1600
1601
	// Find the real head pms!
1602
	if ($context['display_mode'] == 2 && !empty($head_pms))
1603
	{
1604
		$request = $smcFunc['db_query']('', '
1605
			SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
1606
			FROM {db_prefix}personal_messages AS pm
1607
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
1608
			WHERE pm.id_pm_head IN ({array_int:head_pms})
1609
				AND pmr.id_member = {int:current_member}
1610
				AND pmr.deleted = {int:not_deleted}
1611
			GROUP BY pm.id_pm_head
1612
			LIMIT {int:limit}',
1613
			array(
1614
				'head_pms' => array_unique($head_pms),
1615
				'current_member' => $user_info['id'],
1616
				'not_deleted' => 0,
1617
				'limit' => count($head_pms),
1618
			)
1619
		);
1620
		$real_pm_ids = array();
1621
		while ($row = $smcFunc['db_fetch_assoc']($request))
1622
			$real_pm_ids[$row['id_pm_head']] = $row['id_pm'];
1623
		$smcFunc['db_free_result']($request);
1624
	}
1625
1626
	// Load the users...
1627
	loadMemberData($posters);
1628
1629
	// Sort out the page index.
1630
	$context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=search2;params=' . $context['params'], $_GET['start'], $numResults, $modSettings['search_results_per_page'], false);
1631
1632
	$context['num_results'] = $numResults;
1633
1634
	$context['message_labels'] = array();
1635
	$context['message_replied'] = array();
1636
	$context['personal_messages'] = array();
1637
1638
	if (!empty($foundMessages))
1639
	{
1640
		// Now get recipients (but don't include bcc-recipients for your inbox, you're not supposed to know :P!)
1641
		$request = $smcFunc['db_query']('', '
1642
			SELECT
1643
				pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name,
1644
				pmr.bcc, pmr.in_inbox, pmr.is_read
1645
			FROM {db_prefix}pm_recipients AS pmr
1646
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
1647
			WHERE pmr.id_pm IN ({array_int:message_list})',
1648
			array(
1649
				'message_list' => $foundMessages,
1650
			)
1651
		);
1652
		while ($row = $smcFunc['db_fetch_assoc']($request))
1653
		{
1654
			if ($context['folder'] == 'sent' || empty($row['bcc']))
1655
				$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>';
1656
1657
			if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
1658
			{
1659
				$context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
1660
1661
				$row['labels'] = '';
1662
1663
				// Get the labels for this PM
1664
				$request2 = $smcFunc['db_query']('', '
1665
					SELECT id_label
1666
					FROM {db_prefix}pm_labeled_messages
1667
					WHERE id_pm = {int:current_pm}',
1668
					array(
1669
						'current_pm' => $row['id_pm'],
1670
					)
1671
				);
1672
1673
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
1674
				{
1675
					$l_id = $row2['id_label'];
1676
					if (isset($context['labels'][$l_id]))
1677
						$context['message_labels'][$row['id_pm']][$l_id] = array('id' => $l_id, 'name' => $context['labels'][$l_id]['name']);
1678
1679
					// Here we find the first label on a message - for linking to posts in results
1680
					if (!isset($context['first_label'][$row['id_pm']]) && $row['in_inbox'] != 1)
1681
						$context['first_label'][$row['id_pm']] = $l_id;
1682
				}
1683
1684
				$smcFunc['db_free_result']($request2);
1685
1686
				// Is this in the inbox as well?
1687
				if ($row['in_inbox'] == 1)
1688
				{
1689
					$context['message_labels'][$row['id_pm']][-1] = array('id' => -1, 'name' => $context['labels'][-1]['name']);
1690
				}
1691
1692
				$row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']);
1693
			}
1694
		}
1695
1696
		// Prepare the query for the callback!
1697
		$request = $smcFunc['db_query']('', '
1698
			SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
1699
			FROM {db_prefix}personal_messages AS pm
1700
			WHERE pm.id_pm IN ({array_int:message_list})
1701
			ORDER BY {raw:sort} {raw:sort_dir}
1702
			LIMIT {int:limit}',
1703
			array(
1704
				'message_list' => $foundMessages,
1705
				'limit' => count($foundMessages),
1706
				'sort' => $search_params['sort'],
1707
				'sort_dir' => $search_params['sort_dir'],
1708
			)
1709
		);
1710
		$counter = 0;
1711
		while ($row = $smcFunc['db_fetch_assoc']($request))
1712
		{
1713
			// If there's no message subject, use the default.
1714
			$row['subject'] = $row['subject'] == '' ? $txt['no_subject'] : $row['subject'];
1715
1716
			// Load this posters context info, if it ain't there then fill in the essentials...
1717
			if (!loadMemberContext($row['id_member_from'], true))
1718
			{
1719
				$memberContext[$row['id_member_from']]['name'] = $row['from_name'];
1720
				$memberContext[$row['id_member_from']]['id'] = 0;
1721
				$memberContext[$row['id_member_from']]['group'] = $txt['guest_title'];
1722
				$memberContext[$row['id_member_from']]['link'] = $row['from_name'];
1723
				$memberContext[$row['id_member_from']]['email'] = '';
1724
				$memberContext[$row['id_member_from']]['is_guest'] = true;
1725
			}
1726
			else
1727
			{
1728
				$memberContext[$row['id_member_from']]['can_view_profile'] = allowedTo('profile_view') || ($row['id_member_from'] == $user_info['id'] && !$user_info['is_guest']);
1729
				$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'])));
1730
				// Show the email if it's your own PM
1731
				$memberContext[$row['id_member_from']]['show_email'] |= $row['id_member_from'] == $user_info['id'];
1732
			}
1733
1734
			$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']);
1735
1736
			// Censor anything we don't want to see...
1737
			censorText($row['body']);
1738
			censorText($row['subject']);
1739
1740
			// Parse out any BBC...
1741
			$row['body'] = parse_bbc($row['body'], true, 'pm' . $row['id_pm']);
1742
1743
			$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'];
1744
			$context['personal_messages'][] = array(
1745
				'id' => $row['id_pm'],
1746
				'member' => &$memberContext[$row['id_member_from']],
1747
				'subject' => $row['subject'],
1748
				'body' => $row['body'],
1749
				'time' => timeformat($row['msgtime']),
1750
				'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...
1751
				'labels' => &$context['message_labels'][$row['id_pm']],
1752
				'fully_labeled' => count($context['message_labels'][$row['id_pm']]) == count($context['labels']),
1753
				'is_replied_to' => &$context['message_replied'][$row['id_pm']],
1754
				'href' => $href,
1755
				'link' => '<a href="' . $href . '">' . $row['subject'] . '</a>',
1756
				'counter' => ++$counter,
1757
				'can_see_ip' => allowedTo('moderate_forum') || ($row['id_member_from'] == $user_info['id'] && !empty($user_info['id'])),
1758
				'quickbuttons' => array(
1759
					'reply_to_all' => array(
1760
						'label' => $txt['reply_to_all'],
1761
						'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',
1762
						'icon' => 'reply_all_button',
1763
						'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']),
1764
					),
1765
					'reply' => array(
1766
						'label' => $txt['reply'],
1767
						'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'],
1768
						'icon' => 'reply_button',
1769
						'show' => $context['can_send_pm'] && !$memberContext[$row['id_member_from']]['is_guest'] && $row['id_member_from'] != $user_info['id'],
1770
					),
1771
					'quote' => array(
1772
						'label' => $txt['quote_action'],
1773
						'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'] : '')),
1774
						'icon' => 'quote',
1775
						'show' => $context['can_send_pm'],
1776
					),
1777
					'delete' => array(
1778
						'label' => $txt['delete'],
1779
						'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'],
1780
						'javascript' => 'data-confirm="' . JavaScriptEscape($txt['remove_message_question']) . '"',
1781
						'class' => 'you_sure',
1782
						'icon' => 'remove_button',
1783
					),
1784
					'more' => array(
1785
						'report' => array(
1786
							'label' => $txt['pm_report_to_admin'],
1787
							'href' => $scripturl . '?action=pm;sa=report;l=' . $context['current_label_id'] . ';pmsg=' . $row['id_pm'],
1788
							'icon' => 'error',
1789
							'show' => !empty($modSettings['enableReportPM']),
1790
						),
1791
					),
1792
				)
1793
			);
1794
		}
1795
		$smcFunc['db_free_result']($request);
1796
	}
1797
1798
	call_integration_hook('integrate_search_pm_context');
1799
1800
	// Finish off the context.
1801
	$context['page_title'] = $txt['pm_search_title'];
1802
	$context['sub_template'] = 'search_results';
1803
	$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'search';
1804
	$context['linktree'][] = array(
1805
		'url' => $scripturl . '?action=pm;sa=search',
1806
		'name' => $txt['pm_search_bar_title'],
1807
	);
1808
}
1809
1810
/**
1811
 * Send a new message?
1812
 */
1813
function MessagePost()
1814
{
1815
	global $txt, $sourcedir, $scripturl, $modSettings;
1816
	global $context, $smcFunc, $language, $user_info;
1817
1818
	isAllowedTo('pm_send');
1819
1820
	loadLanguage('PersonalMessage');
1821
	// Just in case it was loaded from somewhere else.
1822
	loadTemplate('PersonalMessage');
1823
	loadJavaScriptFile('PersonalMessage.js', array('defer' => false, 'minimize' => true), 'smf_pms');
1824
	loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
1825
	if ($context['drafts_autosave'])
1826
		loadJavaScriptFile('drafts.js', array('defer' => false, 'minimize' => true), 'smf_drafts');
1827
	$context['sub_template'] = 'send';
1828
1829
	// Extract out the spam settings - cause it's neat.
1830
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
1831
1832
	// Set the title...
1833
	$context['page_title'] = $txt['send_message'];
1834
1835
	$context['reply'] = isset($_REQUEST['pmsg']) || isset($_REQUEST['quote']);
1836
1837
	// Check whether we've gone over the limit of messages we can send per hour.
1838
	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')
1839
	{
1840
		// How many messages have they sent this last hour?
1841
		$request = $smcFunc['db_query']('', '
1842
			SELECT COUNT(pr.id_pm) AS post_count
1843
			FROM {db_prefix}personal_messages AS pm
1844
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
1845
			WHERE pm.id_member_from = {int:current_member}
1846
				AND pm.msgtime > {int:msgtime}',
1847
			array(
1848
				'current_member' => $user_info['id'],
1849
				'msgtime' => time() - 3600,
1850
			)
1851
		);
1852
		list ($postCount) = $smcFunc['db_fetch_row']($request);
1853
		$smcFunc['db_free_result']($request);
1854
1855
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
1856
			fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
1857
	}
1858
1859
	// Quoting/Replying to a message?
1860
	if (!empty($_REQUEST['pmsg']))
1861
	{
1862
		$pmsg = (int) $_REQUEST['pmsg'];
1863
1864
		// Make sure this is yours.
1865
		if (!isAccessiblePM($pmsg))
1866
			fatal_lang_error('no_access', false);
1867
1868
		// Work out whether this is one you've received?
1869
		$request = $smcFunc['db_query']('', '
1870
			SELECT
1871
				id_pm
1872
			FROM {db_prefix}pm_recipients
1873
			WHERE id_pm = {int:id_pm}
1874
				AND id_member = {int:current_member}
1875
			LIMIT 1',
1876
			array(
1877
				'current_member' => $user_info['id'],
1878
				'id_pm' => $pmsg,
1879
			)
1880
		);
1881
		$isReceived = $smcFunc['db_num_rows']($request) != 0;
1882
		$smcFunc['db_free_result']($request);
1883
1884
		// Get the quoted message (and make sure you're allowed to see this quote!).
1885
		$request = $smcFunc['db_query']('', '
1886
			SELECT
1887
				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,
1888
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
1889
				COALESCE(mem.real_name, pm.from_name) AS real_name
1890
			FROM {db_prefix}personal_messages AS pm' . (!$isReceived ? '' : '
1891
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:id_pm})') . '
1892
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
1893
			WHERE pm.id_pm = {int:id_pm}' . (!$isReceived ? '
1894
				AND pm.id_member_from = {int:current_member}' : '
1895
				AND pmr.id_member = {int:current_member}') . '
1896
			LIMIT 1',
1897
			array(
1898
				'current_member' => $user_info['id'],
1899
				'id_pm_head_empty' => 0,
1900
				'id_pm' => $pmsg,
1901
			)
1902
		);
1903
		if ($smcFunc['db_num_rows']($request) == 0)
1904
			fatal_lang_error('pm_not_yours', false);
1905
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
1906
		$smcFunc['db_free_result']($request);
1907
1908
		// Censor the message.
1909
		censorText($row_quoted['subject']);
1910
		censorText($row_quoted['body']);
1911
1912
		// Add 'Re: ' to it....
1913
		if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix')))
1914
		{
1915
			if ($language === $user_info['language'])
1916
				$context['response_prefix'] = $txt['response_prefix'];
1917
			else
1918
			{
1919
				loadLanguage('index', $language, false);
1920
				$context['response_prefix'] = $txt['response_prefix'];
1921
				loadLanguage('index');
1922
			}
1923
			cache_put_data('response_prefix', $context['response_prefix'], 600);
1924
		}
1925
		$form_subject = $row_quoted['subject'];
1926
		if ($context['reply'] && trim($context['response_prefix']) != '' && $smcFunc['strpos']($form_subject, trim($context['response_prefix'])) !== 0)
1927
			$form_subject = $context['response_prefix'] . $form_subject;
1928
1929
		if (isset($_REQUEST['quote']))
1930
		{
1931
			// Remove any nested quotes and <br>...
1932
			$form_message = preg_replace('~<br ?/?' . '>~i', "\n", $row_quoted['body']);
1933
			if (!empty($modSettings['removeNestedQuotes']))
1934
				$form_message = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $form_message);
1935
			if (empty($row_quoted['id_member']))
1936
				$form_message = '[quote author=&quot;' . $row_quoted['real_name'] . '&quot;]' . "\n" . $form_message . "\n" . '[/quote]';
1937
			else
1938
				$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]';
1939
		}
1940
		else
1941
			$form_message = '';
1942
1943
		// Do the BBC thang on the message.
1944
		$row_quoted['body'] = parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']);
1945
1946
		// Set up the quoted message array.
1947
		$context['quoted_message'] = array(
1948
			'id' => $row_quoted['id_pm'],
1949
			'pm_head' => $row_quoted['pm_head'],
1950
			'member' => array(
1951
				'name' => $row_quoted['real_name'],
1952
				'username' => $row_quoted['member_name'],
1953
				'id' => $row_quoted['id_member'],
1954
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
1955
				'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'],
1956
			),
1957
			'subject' => $row_quoted['subject'],
1958
			'time' => timeformat($row_quoted['msgtime']),
1959
			'timestamp' => $row_quoted['msgtime'],
1960
			'body' => $row_quoted['body']
1961
		);
1962
	}
1963
	else
1964
	{
1965
		$context['quoted_message'] = false;
1966
		$form_subject = '';
1967
		$form_message = '';
1968
	}
1969
1970
	$context['recipients'] = array(
1971
		'to' => array(),
1972
		'bcc' => array(),
1973
	);
1974
1975
	// Sending by ID?  Replying to all?  Fetch the real_name(s).
1976
	if (isset($_REQUEST['u']))
1977
	{
1978
		// If the user is replying to all, get all the other members this was sent to..
1979
		if ($_REQUEST['u'] == 'all' && isset($row_quoted))
1980
		{
1981
			// Firstly, to reply to all we clearly already have $row_quoted - so have the original member from.
1982
			if ($row_quoted['id_member'] != $user_info['id'])
1983
				$context['recipients']['to'][] = array(
1984
					'id' => $row_quoted['id_member'],
1985
					'name' => $smcFunc['htmlspecialchars']($row_quoted['real_name']),
1986
				);
1987
1988
			// Now to get the others.
1989
			$request = $smcFunc['db_query']('', '
1990
				SELECT mem.id_member, mem.real_name
1991
				FROM {db_prefix}pm_recipients AS pmr
1992
					INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
1993
				WHERE pmr.id_pm = {int:id_pm}
1994
					AND pmr.id_member != {int:current_member}
1995
					AND pmr.bcc = {int:not_bcc}',
1996
				array(
1997
					'current_member' => $user_info['id'],
1998
					'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...
1999
					'not_bcc' => 0,
2000
				)
2001
			);
2002
			while ($row = $smcFunc['db_fetch_assoc']($request))
2003
				$context['recipients']['to'][] = array(
2004
					'id' => $row['id_member'],
2005
					'name' => $row['real_name'],
2006
				);
2007
			$smcFunc['db_free_result']($request);
2008
		}
2009
		else
2010
		{
2011
			$_REQUEST['u'] = explode(',', $_REQUEST['u']);
2012
			foreach ($_REQUEST['u'] as $key => $uID)
2013
				$_REQUEST['u'][$key] = (int) $uID;
2014
2015
			$_REQUEST['u'] = array_unique($_REQUEST['u']);
2016
2017
			$request = $smcFunc['db_query']('', '
2018
				SELECT id_member, real_name
2019
				FROM {db_prefix}members
2020
				WHERE id_member IN ({array_int:member_list})
2021
				LIMIT {int:limit}',
2022
				array(
2023
					'member_list' => $_REQUEST['u'],
2024
					'limit' => count($_REQUEST['u']),
2025
				)
2026
			);
2027
			while ($row = $smcFunc['db_fetch_assoc']($request))
2028
				$context['recipients']['to'][] = array(
2029
					'id' => $row['id_member'],
2030
					'name' => $row['real_name'],
2031
				);
2032
			$smcFunc['db_free_result']($request);
2033
		}
2034
2035
		// Get a literal name list in case the user has JavaScript disabled.
2036
		$names = array();
2037
		foreach ($context['recipients']['to'] as $to)
2038
			$names[] = $to['name'];
2039
		$context['to_value'] = empty($names) ? '' : '&quot;' . implode('&quot;, &quot;', $names) . '&quot;';
2040
	}
2041
	else
2042
		$context['to_value'] = '';
2043
2044
	// Set the defaults...
2045
	$context['subject'] = $form_subject;
2046
	$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
2047
	$context['post_error'] = array();
2048
2049
	// And build the link tree.
2050
	$context['linktree'][] = array(
2051
		'url' => $scripturl . '?action=pm;sa=send',
2052
		'name' => $txt['new_message']
2053
	);
2054
2055
	// Generate a list of drafts that they can load in to the editor
2056
	if (!empty($context['drafts_pm_save']))
2057
	{
2058
		require_once($sourcedir . '/Drafts.php');
2059
		$pm_seed = isset($_REQUEST['pmsg']) ? $_REQUEST['pmsg'] : (isset($_REQUEST['quote']) ? $_REQUEST['quote'] : 0);
2060
		ShowDrafts($user_info['id'], $pm_seed, 1);
2061
	}
2062
2063
	// Needed for the WYSIWYG editor.
2064
	require_once($sourcedir . '/Subs-Editor.php');
2065
2066
	// Now create the editor.
2067
	$editorOptions = array(
2068
		'id' => 'message',
2069
		'value' => $context['message'],
2070
		'height' => '175px',
2071
		'width' => '100%',
2072
		'labels' => array(
2073
			'post_button' => $txt['send_message'],
2074
		),
2075
		'preview_type' => 2,
2076
		'required' => true,
2077
	);
2078
	create_control_richedit($editorOptions);
2079
2080
	// Store the ID for old compatibility.
2081
	$context['post_box_name'] = $editorOptions['id'];
2082
2083
	$context['bcc_value'] = '';
2084
2085
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
2086
	if ($context['require_verification'])
2087
	{
2088
		$verificationOptions = array(
2089
			'id' => 'pm',
2090
		);
2091
		$context['require_verification'] = create_control_verification($verificationOptions);
2092
		$context['visual_verification_id'] = $verificationOptions['id'];
2093
	}
2094
2095
	call_integration_hook('integrate_pm_post');
2096
2097
	// Register this form and get a sequence number in $context.
2098
	checkSubmitOnce('register');
2099
}
2100
2101
/**
2102
 * This function allows the user to view their PM drafts
2103
 */
2104
function MessageDrafts()
2105
{
2106
	global $sourcedir, $user_info;
2107
2108
	// validate with loadMemberData()
2109
	$memberResult = loadMemberData($user_info['id'], false);
2110
	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...
2111
		fatal_lang_error('not_a_user', false);
2112
	list ($memID) = $memberResult;
2113
2114
	// drafts is where the functions reside
2115
	require_once($sourcedir . '/Drafts.php');
2116
	showPMDrafts($memID);
2117
}
2118
2119
/**
2120
 * An error in the message...
2121
 *
2122
 * @param array $error_types An array of strings indicating which type of errors occurred
2123
 * @param array $named_recipients
2124
 * @param $recipient_ids
2125
 */
2126
function messagePostError($error_types, $named_recipients, $recipient_ids = array())
2127
{
2128
	global $txt, $context, $scripturl, $modSettings;
2129
	global $smcFunc, $user_info, $sourcedir;
2130
2131
	if (!isset($_REQUEST['xml']))
2132
	{
2133
		$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'send';
2134
		$context['sub_template'] = 'send';
2135
		loadJavaScriptFile('PersonalMessage.js', array('defer' => false, 'minimize' => true), 'smf_pms');
2136
		loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
2137
	}
2138
	else
2139
		$context['sub_template'] = 'pm';
2140
2141
	$context['page_title'] = $txt['send_message'];
2142
2143
	// Got some known members?
2144
	$context['recipients'] = array(
2145
		'to' => array(),
2146
		'bcc' => array(),
2147
	);
2148
	if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc']))
2149
	{
2150
		$allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']);
2151
2152
		$request = $smcFunc['db_query']('', '
2153
			SELECT id_member, real_name
2154
			FROM {db_prefix}members
2155
			WHERE id_member IN ({array_int:member_list})',
2156
			array(
2157
				'member_list' => $allRecipients,
2158
			)
2159
		);
2160
		while ($row = $smcFunc['db_fetch_assoc']($request))
2161
		{
2162
			$recipientType = in_array($row['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to';
2163
			$context['recipients'][$recipientType][] = array(
2164
				'id' => $row['id_member'],
2165
				'name' => $row['real_name'],
2166
			);
2167
		}
2168
		$smcFunc['db_free_result']($request);
2169
	}
2170
2171
	// Set everything up like before....
2172
	$context['subject'] = isset($_REQUEST['subject']) ? $smcFunc['htmlspecialchars']($_REQUEST['subject']) : '';
2173
	$context['message'] = isset($_REQUEST['message']) ? str_replace(array('  '), array('&nbsp; '), $smcFunc['htmlspecialchars']($_REQUEST['message'])) : '';
2174
	$context['reply'] = !empty($_REQUEST['replied_to']);
2175
2176
	if ($context['reply'])
2177
	{
2178
		$_REQUEST['replied_to'] = (int) $_REQUEST['replied_to'];
2179
2180
		$request = $smcFunc['db_query']('', '
2181
			SELECT
2182
				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,
2183
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
2184
				COALESCE(mem.real_name, pm.from_name) AS real_name
2185
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' : '
2186
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:replied_to})') . '
2187
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
2188
			WHERE pm.id_pm = {int:replied_to}' . ($context['folder'] == 'sent' ? '
2189
				AND pm.id_member_from = {int:current_member}' : '
2190
				AND pmr.id_member = {int:current_member}') . '
2191
			LIMIT 1',
2192
			array(
2193
				'current_member' => $user_info['id'],
2194
				'no_id_pm_head' => 0,
2195
				'replied_to' => $_REQUEST['replied_to'],
2196
			)
2197
		);
2198
		if ($smcFunc['db_num_rows']($request) == 0)
2199
		{
2200
			if (!isset($_REQUEST['xml']))
2201
				fatal_lang_error('pm_not_yours', false);
2202
			else
2203
				$error_types[] = 'pm_not_yours';
2204
		}
2205
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
2206
		$smcFunc['db_free_result']($request);
2207
2208
		censorText($row_quoted['subject']);
2209
		censorText($row_quoted['body']);
2210
2211
		$context['quoted_message'] = array(
2212
			'id' => $row_quoted['id_pm'],
2213
			'pm_head' => $row_quoted['pm_head'],
2214
			'member' => array(
2215
				'name' => $row_quoted['real_name'],
2216
				'username' => $row_quoted['member_name'],
2217
				'id' => $row_quoted['id_member'],
2218
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
2219
				'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'],
2220
			),
2221
			'subject' => $row_quoted['subject'],
2222
			'time' => timeformat($row_quoted['msgtime']),
2223
			'timestamp' => $row_quoted['msgtime'],
2224
			'body' => parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']),
2225
		);
2226
	}
2227
2228
	// Build the link tree....
2229
	$context['linktree'][] = array(
2230
		'url' => $scripturl . '?action=pm;sa=send',
2231
		'name' => $txt['new_message']
2232
	);
2233
2234
	// Set each of the errors for the template.
2235
	loadLanguage('Errors');
2236
2237
	$context['error_type'] = 'minor';
2238
2239
	$context['post_error'] = array(
2240
		'messages' => array(),
2241
		// @todo error handling: maybe fatal errors can be error_type => serious
2242
		'error_type' => '',
2243
	);
2244
2245
	foreach ($error_types as $error_type)
2246
	{
2247
		$context['post_error'][$error_type] = true;
2248
		if (isset($txt['error_' . $error_type]))
2249
		{
2250
			if ($error_type == 'long_message')
2251
				$txt['error_' . $error_type] = sprintf($txt['error_' . $error_type], $modSettings['max_messageLength']);
2252
2253
			$context['post_error']['messages'][] = $txt['error_' . $error_type];
2254
		}
2255
2256
		// If it's not a minor error flag it as such.
2257
		if (!in_array($error_type, array('new_reply', 'not_approved', 'new_replies', 'old_topic', 'need_qr_verification', 'no_subject')))
2258
			$context['error_type'] = 'serious';
2259
	}
2260
2261
	// We need to load the editor once more.
2262
	require_once($sourcedir . '/Subs-Editor.php');
2263
2264
	// Create it...
2265
	$editorOptions = array(
2266
		'id' => 'message',
2267
		'value' => $context['message'],
2268
		'width' => '90%',
2269
		'height' => '175px',
2270
		'labels' => array(
2271
			'post_button' => $txt['send_message'],
2272
		),
2273
		'preview_type' => 2,
2274
	);
2275
	create_control_richedit($editorOptions);
2276
2277
	// ... and store the ID again...
2278
	$context['post_box_name'] = $editorOptions['id'];
2279
2280
	// Check whether we need to show the code again.
2281
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
2282
	if ($context['require_verification'] && !isset($_REQUEST['xml']))
2283
	{
2284
		require_once($sourcedir . '/Subs-Editor.php');
2285
		$verificationOptions = array(
2286
			'id' => 'pm',
2287
		);
2288
		$context['require_verification'] = create_control_verification($verificationOptions);
2289
		$context['visual_verification_id'] = $verificationOptions['id'];
2290
	}
2291
2292
	$context['to_value'] = empty($named_recipients['to']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['to']) . '&quot;';
2293
	$context['bcc_value'] = empty($named_recipients['bcc']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['bcc']) . '&quot;';
2294
2295
	call_integration_hook('integrate_pm_error');
2296
2297
	// No check for the previous submission is needed.
2298
	checkSubmitOnce('free');
2299
2300
	// Acquire a new form sequence number.
2301
	checkSubmitOnce('register');
2302
}
2303
2304
/**
2305
 * Send it!
2306
 */
2307
function MessagePost2()
2308
{
2309
	global $txt, $context, $sourcedir;
2310
	global $user_info, $modSettings, $smcFunc;
2311
2312
	isAllowedTo('pm_send');
2313
	require_once($sourcedir . '/Subs-Auth.php');
2314
2315
	// PM Drafts enabled and needed?
2316
	if ($context['drafts_pm_save'] && (isset($_POST['save_draft']) || isset($_POST['id_pm_draft'])))
2317
	{
2318
		$context['id_pm_draft'] = !empty($_POST['id_pm_draft']) ? (int) $_POST['id_pm_draft'] : 0;
2319
		require_once($sourcedir . '/Drafts.php');
2320
	}
2321
2322
	loadLanguage('PersonalMessage', '', false);
2323
2324
	// Extract out the spam settings - it saves database space!
2325
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
2326
2327
	// Initialize the errors we're about to make.
2328
	$post_errors = array();
2329
2330
	// Check whether we've gone over the limit of messages we can send per hour - fatal error if fails!
2331
	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')
2332
	{
2333
		// How many have they sent this last hour?
2334
		$request = $smcFunc['db_query']('', '
2335
			SELECT COUNT(pr.id_pm) AS post_count
2336
			FROM {db_prefix}personal_messages AS pm
2337
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
2338
			WHERE pm.id_member_from = {int:current_member}
2339
				AND pm.msgtime > {int:msgtime}',
2340
			array(
2341
				'current_member' => $user_info['id'],
2342
				'msgtime' => time() - 3600,
2343
			)
2344
		);
2345
		list ($postCount) = $smcFunc['db_fetch_row']($request);
2346
		$smcFunc['db_free_result']($request);
2347
2348
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
2349
		{
2350
			if (!isset($_REQUEST['xml']))
2351
				fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
2352
			else
2353
				$post_errors[] = 'pm_too_many_per_hour';
2354
		}
2355
	}
2356
2357
	// If your session timed out, show an error, but do allow to re-submit.
2358
	if (!isset($_REQUEST['xml']) && checkSession('post', '', false) != '')
2359
		$post_errors[] = 'session_timeout';
2360
2361
	$_REQUEST['subject'] = isset($_REQUEST['subject']) ? trim($_REQUEST['subject']) : '';
2362
	$_REQUEST['to'] = empty($_POST['to']) ? (empty($_GET['to']) ? '' : $_GET['to']) : $_POST['to'];
2363
	$_REQUEST['bcc'] = empty($_POST['bcc']) ? (empty($_GET['bcc']) ? '' : $_GET['bcc']) : $_POST['bcc'];
2364
2365
	// Route the input from the 'u' parameter to the 'to'-list.
2366
	if (!empty($_POST['u']))
2367
		$_POST['recipient_to'] = explode(',', $_POST['u']);
2368
2369
	// Construct the list of recipients.
2370
	$recipientList = array();
2371
	$namedRecipientList = array();
2372
	$namesNotFound = array();
2373
	foreach (array('to', 'bcc') as $recipientType)
2374
	{
2375
		// First, let's see if there's user ID's given.
2376
		$recipientList[$recipientType] = array();
2377
		if (!empty($_POST['recipient_' . $recipientType]) && is_array($_POST['recipient_' . $recipientType]))
2378
		{
2379
			foreach ($_POST['recipient_' . $recipientType] as $recipient)
2380
				$recipientList[$recipientType][] = (int) $recipient;
2381
		}
2382
2383
		// Are there also literal names set?
2384
		if (!empty($_REQUEST[$recipientType]))
2385
		{
2386
			// We're going to take out the "s anyway ;).
2387
			$recipientString = strtr($_REQUEST[$recipientType], array('\\"' => '"'));
2388
2389
			preg_match_all('~"([^"]+)"~', $recipientString, $matches);
2390
			$namedRecipientList[$recipientType] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))));
2391
2392
			foreach ($namedRecipientList[$recipientType] as $index => $recipient)
2393
			{
2394
				if (strlen(trim($recipient)) > 0)
2395
					$namedRecipientList[$recipientType][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($recipient)));
2396
				else
2397
					unset($namedRecipientList[$recipientType][$index]);
2398
			}
2399
2400
			if (!empty($namedRecipientList[$recipientType]))
2401
			{
2402
				$foundMembers = findMembers($namedRecipientList[$recipientType]);
2403
2404
				// Assume all are not found, until proven otherwise.
2405
				$namesNotFound[$recipientType] = $namedRecipientList[$recipientType];
2406
2407
				foreach ($foundMembers as $member)
2408
				{
2409
					$testNames = array(
2410
						$smcFunc['strtolower']($member['username']),
2411
						$smcFunc['strtolower']($member['name']),
2412
						$smcFunc['strtolower']($member['email']),
2413
					);
2414
2415
					if (count(array_intersect($testNames, $namedRecipientList[$recipientType])) !== 0)
2416
					{
2417
						$recipientList[$recipientType][] = $member['id'];
2418
2419
						// Get rid of this username, since we found it.
2420
						$namesNotFound[$recipientType] = array_diff($namesNotFound[$recipientType], $testNames);
2421
					}
2422
				}
2423
			}
2424
		}
2425
2426
		// Selected a recipient to be deleted? Remove them now.
2427
		if (!empty($_POST['delete_recipient']))
2428
			$recipientList[$recipientType] = array_diff($recipientList[$recipientType], array((int) $_POST['delete_recipient']));
2429
2430
		// Make sure we don't include the same name twice
2431
		$recipientList[$recipientType] = array_unique($recipientList[$recipientType]);
2432
	}
2433
2434
	// Are we changing the recipients some how?
2435
	$is_recipient_change = !empty($_POST['delete_recipient']) || !empty($_POST['to_submit']) || !empty($_POST['bcc_submit']);
2436
2437
	// Check if there's at least one recipient.
2438
	if (empty($recipientList['to']) && empty($recipientList['bcc']))
2439
		$post_errors[] = 'no_to';
2440
2441
	// Make sure that we remove the members who did get it from the screen.
2442
	if (!$is_recipient_change)
2443
	{
2444
		foreach ($recipientList as $recipientType => $dummy)
2445
		{
2446
			if (!empty($namesNotFound[$recipientType]))
2447
			{
2448
				$post_errors[] = 'bad_' . $recipientType;
2449
2450
				// Since we already have a post error, remove the previous one.
2451
				$post_errors = array_diff($post_errors, array('no_to'));
2452
2453
				foreach ($namesNotFound[$recipientType] as $name)
2454
					$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
2455
			}
2456
		}
2457
	}
2458
2459
	// Did they make any mistakes?
2460
	if ($_REQUEST['subject'] == '')
2461
		$post_errors[] = 'no_subject';
2462
	if (!isset($_REQUEST['message']) || $_REQUEST['message'] == '')
2463
		$post_errors[] = 'no_message';
2464
	elseif (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_REQUEST['message']) > $modSettings['max_messageLength'])
2465
		$post_errors[] = 'long_message';
2466
	else
2467
	{
2468
		// Preparse the message.
2469
		$message = $_REQUEST['message'];
2470
		preparsecode($message);
2471
2472
		// Make sure there's still some content left without the tags.
2473
		if ($smcFunc['htmltrim'](strip_tags(parse_bbc($smcFunc['htmlspecialchars']($message, ENT_QUOTES), false), '<img>')) === '' && (!allowedTo('bbc_html') || strpos($message, '[html]') === false))
2474
			$post_errors[] = 'no_message';
2475
	}
2476
2477
	// Wrong verification code?
2478
	if (!$user_info['is_admin'] && !isset($_REQUEST['xml']) && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'])
2479
	{
2480
		require_once($sourcedir . '/Subs-Editor.php');
2481
		$verificationOptions = array(
2482
			'id' => 'pm',
2483
		);
2484
		$context['require_verification'] = create_control_verification($verificationOptions, true);
2485
2486
		if (is_array($context['require_verification']))
2487
			$post_errors = array_merge($post_errors, $context['require_verification']);
2488
	}
2489
2490
	// If they did, give a chance to make ammends.
2491
	if (!empty($post_errors) && !$is_recipient_change && !isset($_REQUEST['preview']) && !isset($_REQUEST['xml']))
2492
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
0 ignored issues
show
Bug introduced by
Are you sure the usage of messagePostError($post_e...ntList, $recipientList) is correct as it seems to always return null.

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

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

}

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

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

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

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

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

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

}

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

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

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

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

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

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

}

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

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

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

Loading history...
2528
	}
2529
2530
	// Want to save this as a draft and think about it some more?
2531
	if ($context['drafts_pm_save'] && isset($_POST['save_draft']))
2532
	{
2533
		SavePMDraft($post_errors, $recipientList);
2534
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
0 ignored issues
show
Bug introduced by
Are you sure the usage of messagePostError($post_e...ntList, $recipientList) is correct as it seems to always return null.

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

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

}

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

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

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

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

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

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

}

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

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

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

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

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

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

}

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

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

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

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