MessagePost()   F
last analyzed

Complexity

Conditions 40

Size

Total Lines 286
Code Lines 157

Duplication

Lines 0
Ratio 0 %

Importance

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

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

Loading history...
1028
		return @$smcFunc['db_data_seek']($messages_request, 0);
1029
1030
	// Get the next one... bail if anything goes wrong.
1031
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1032
	if (!$message)
1033
	{
1034
		if ($type != 'subject')
1035
			$smcFunc['db_free_result']($messages_request);
1036
1037
		return false;
1038
	}
1039
1040
	// Use '(no subject)' if none was specified.
1041
	$message['subject'] = $message['subject'] == '' ? $txt['no_subject'] : $message['subject'];
1042
1043
	// Load the message's information - if it's not there, load the guest information.
1044
	if (!loadMemberContext($message['id_member_from'], true))
1045
	{
1046
		$memberContext[$message['id_member_from']]['name'] = $message['from_name'];
1047
		$memberContext[$message['id_member_from']]['id'] = 0;
1048
1049
		// Sometimes the forum sends messages itself (Warnings are an example) - in this case don't label it from a guest.
1050
		$memberContext[$message['id_member_from']]['group'] = $message['from_name'] == $context['forum_name_html_safe'] ? '' : $txt['guest_title'];
1051
		$memberContext[$message['id_member_from']]['link'] = $message['from_name'];
1052
		$memberContext[$message['id_member_from']]['email'] = '';
1053
		$memberContext[$message['id_member_from']]['show_email'] = false;
1054
		$memberContext[$message['id_member_from']]['is_guest'] = true;
1055
	}
1056
	else
1057
	{
1058
		$memberContext[$message['id_member_from']]['can_view_profile'] = allowedTo('profile_view') || ($message['id_member_from'] == $user_info['id'] && !$user_info['is_guest']);
1059
		$memberContext[$message['id_member_from']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member_from']]['warning_status'] && ($context['user']['can_mod'] || (!empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $message['id_member_from'] == $user_info['id'])));
1060
		// Show the email if it's your own PM
1061
		$memberContext[$message['id_member_from']]['show_email'] |= $message['id_member_from'] == $user_info['id'];
1062
	}
1063
1064
	$memberContext[$message['id_member_from']]['show_profile_buttons'] = $modSettings['show_profile_buttons'] && (!empty($memberContext[$message['id_member_from']]['can_view_profile']) || (!empty($memberContext[$message['id_member_from']]['website']['url']) && !isset($context['disabled_fields']['website'])) || $memberContext[$message['id_member_from']]['show_email'] || $context['can_send_pm']);
1065
1066
	// Censor all the important text...
1067
	censorText($message['body']);
1068
	censorText($message['subject']);
1069
1070
	// Run UBBC interpreter on the message.
1071
	$message['body'] = parse_bbc($message['body'], true, 'pm' . $message['id_pm']);
1072
1073
	// Send the array.
1074
	$output = array(
1075
		'id' => $message['id_pm'],
1076
		'member' => &$memberContext[$message['id_member_from']],
1077
		'subject' => $message['subject'],
1078
		'time' => timeformat($message['msgtime']),
1079
		'timestamp' => forum_time(true, $message['msgtime']),
1080
		'counter' => $counter,
1081
		'body' => $message['body'],
1082
		'recipients' => &$recipients[$message['id_pm']],
1083
		'number_recipients' => count($recipients[$message['id_pm']]['to']),
1084
		'labels' => &$context['message_labels'][$message['id_pm']],
1085
		'fully_labeled' => count(isset($context['message_labels'][$message['id_pm']]) ? $context['message_labels'][$message['id_pm']] : array()) == count($context['labels']),
1086
		'is_replied_to' => &$context['message_replied'][$message['id_pm']],
1087
		'is_unread' => &$context['message_unread'][$message['id_pm']],
1088
		'is_selected' => !empty($temp_pm_selected) && in_array($message['id_pm'], $temp_pm_selected),
1089
		'is_message_author' => $message['id_member_from'] == $user_info['id'],
1090
		'can_report' => !empty($modSettings['enableReportPM']),
1091
		'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member_from'] == $user_info['id'] && !empty($user_info['id'])),
1092
	);
1093
1094
	$counter++;
1095
1096
	// Any custom profile fields?
1097
	if (!empty($memberContext[$message['id_member_from']]['custom_fields']))
1098
		foreach ($memberContext[$message['id_member_from']]['custom_fields'] as $custom)
1099
			$output['custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
1100
1101
	call_integration_hook('integrate_prepare_pm_context', array(&$output, &$message, $counter));
1102
1103
	$output['quickbuttons'] = array(
1104
		'reply_to_all' => array(
1105
			'label' => $txt['reply_to_all'],
1106
			'href' => $scripturl.'?action=pm;sa=send;f='.$context['folder'].($context['current_label_id'] != -1 ? ';l=' .$context['current_label_id'] : '').';pmsg='.$output['id'].';quote;u=all',
1107
			'icon' => 'reply_all_button',
1108
			'show' => $context['can_send_pm'] && !$output['member']['is_guest'] && $output['number_recipients'] > 1
1109
		),
1110
		'reply' => array(
1111
			'label' => $txt['reply'],
1112
			'href' => $scripturl.'?action=pm;sa=send;f='.$context['folder'].($context['current_label_id'] != -1 ? ';l=' .$context['current_label_id'] : '').';pmsg='.$output['id'].';u='.$output['member']['id'],
1113
			'icon' => 'reply_button',
1114
			'show' => $context['can_send_pm'] && !$output['member']['is_guest']
1115
		),
1116
		'quote' => array(
1117
			'label' => $txt['quote_action'],
1118
			'href' => $scripturl.'?action=pm;sa=send;f='.$context['folder'].($context['current_label_id'] != -1 ? ';l=' .$context['current_label_id'] : '').';pmsg='.$output['id'].';quote'.($context['folder'] == 'sent' ? '' : ';u=' .$output['member']['id']),
1119
			'icon' => 'quote',
1120
			'show' => $context['can_send_pm'] && !$output['member']['is_guest']
1121
		),
1122
		'reply_quote' => array(
1123
			'label' => $txt['reply_quote'],
1124
			'href' => $scripturl.'?action=pm;sa=send;f='.$context['folder'].($context['current_label_id'] != -1 ? ';l=' .$context['current_label_id'] : '').';pmsg='.$output['id'].';quote',
1125
			'icon' => 'quote',
1126
			'show' => $context['can_send_pm'] && $output['member']['is_guest']
1127
		),
1128
		'delete' => array(
1129
			'label' => $txt['delete'],
1130
			'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'],
1131
			'javascript' => 'data-confirm="'.addslashes($txt['remove_message_question']).'" class="you_sure"',
1132
			'icon' => 'remove_button',
1133
		),
1134
		'more' => array(
1135
			'report' => array(
1136
				'label' => $txt['pm_report_to_admin'],
1137
				'href' => $scripturl .'?action=pm;sa=report;l=' .$context['current_label_id'] .';pmsg=' .$output['id'],
1138
				'icon' => 'error',
1139
				'show' => $output['can_report']
1140
			),
1141
		),
1142
		'quickmod' => array(
1143
			'content' => '<input type="checkbox" name="pms[]" id="deletedisplay'.$output['id'].'" value="'.$output['id'].'" onclick="document.getElementById(\'deletelisting'.$output['id'].'\').checked = this.checked;">',
1144
			'show' => empty($context['display_mode'])
1145
		)
1146
	);
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
	$labelQuery = '';
1366
	$labelJoin = '';
1367
	if ($context['folder'] == 'inbox' && !empty($search_params['advanced']) && $context['currently_using_labels'])
1368
	{
1369
		// Came here from pagination?  Put them back into $_REQUEST for sanitization.
1370
		if (isset($search_params['labels']))
1371
			$_REQUEST['searchlabel'] = explode(',', $search_params['labels']);
1372
1373
		// Assuming we have some labels - make them all integers.
1374
		if (!empty($_REQUEST['searchlabel']) && is_array($_REQUEST['searchlabel']))
1375
		{
1376
			foreach ($_REQUEST['searchlabel'] as $key => $id)
1377
				$_REQUEST['searchlabel'][$key] = (int) $id;
1378
		}
1379
		else
1380
			$_REQUEST['searchlabel'] = array();
1381
1382
		// Now that everything is cleaned up a bit, make the labels a param.
1383
		$search_params['labels'] = implode(',', $_REQUEST['searchlabel']);
1384
1385
		// No labels selected? That must be an error!
1386
		if (empty($_REQUEST['searchlabel']))
1387
			$context['search_errors']['no_labels_selected'] = true;
1388
		// Otherwise prepare the query!
1389
		elseif (count($_REQUEST['searchlabel']) != count($context['labels']))
1390
		{
1391
			// Special case here... "inbox" isn't a real label anymore...
1392
			if (in_array(-1, $_REQUEST['searchlabel']))
1393
			{
1394
				$labelQuery = '	AND pmr.in_inbox = {int:in_inbox}';
1395
				$searchq_parameters['in_inbox'] = 1;
1396
1397
				// Now we get rid of that...
1398
				$temp = array_diff($_REQUEST['searchlabel'], array(-1));
1399
				$_REQUEST['searchlabel'] = $temp;
1400
			}
1401
1402
			// Still have something?
1403
			if (!empty($_REQUEST['searchlabel']))
1404
			{
1405
				if ($labelQuery == '')
1406
				{
1407
					// Not searching the inbox - PM must be labeled
1408
					$labelQuery = ' AND pml.id_label IN ({array_int:labels})';
1409
					$labelJoin = ' INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1410
				}
1411
				else
1412
				{
1413
					// Searching the inbox - PM doesn't have to be labeled
1414
					$labelQuery = ' AND (' . substr($labelQuery, 5) . ' OR pml.id_label IN ({array_int:labels}))';
1415
					$labelJoin = ' LEFT JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1416
				}
1417
1418
				$searchq_parameters['labels'] = $_REQUEST['searchlabel'];
1419
			}
1420
		}
1421
	}
1422
1423
	// What are we actually searching for?
1424
	$search_params['search'] = !empty($search_params['search']) ? $search_params['search'] : (isset($_REQUEST['search']) ? $_REQUEST['search'] : '');
1425
	// If we ain't got nothing - we should error!
1426
	if (!isset($search_params['search']) || $search_params['search'] == '')
1427
		$context['search_errors']['invalid_search_string'] = true;
1428
1429
	// Extract phrase parts first (e.g. some words "this is a phrase" some more words.)
1430
	preg_match_all('~(?:^|\s)([-]?)"([^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), $search_params['search'], $matches, PREG_PATTERN_ORDER);
1431
	$searchArray = $matches[2];
1432
1433
	// Remove the phrase parts and extract the words.
1434
	$tempSearch = explode(' ', preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']));
1435
1436
	// A minus sign in front of a word excludes the word.... so...
1437
	$excludedWords = array();
1438
1439
	// .. first, we check for things like -"some words", but not "-some words".
1440
	foreach ($matches[1] as $index => $word)
1441
		if ($word == '-')
1442
		{
1443
			$word = $smcFunc['strtolower'](trim($searchArray[$index]));
1444
			if (strlen($word) > 0)
1445
				$excludedWords[] = $word;
1446
			unset($searchArray[$index]);
1447
		}
1448
1449
	// Now we look for -test, etc.... normaller.
1450
	foreach ($tempSearch as $index => $word)
1451
	{
1452
		if (strpos(trim($word), '-') === 0)
1453
		{
1454
			$word = substr($smcFunc['strtolower']($word), 1);
1455
			if (strlen($word) > 0)
1456
				$excludedWords[] = $word;
1457
			unset($tempSearch[$index]);
1458
		}
1459
	}
1460
1461
	$searchArray = array_merge($searchArray, $tempSearch);
1462
1463
	// Trim everything and make sure there are no words that are the same.
1464
	foreach ($searchArray as $index => $value)
1465
	{
1466
		$searchArray[$index] = $smcFunc['strtolower'](trim($value));
1467
		if ($searchArray[$index] == '')
1468
			unset($searchArray[$index]);
1469
		else
1470
		{
1471
			// Sort out entities first.
1472
			$searchArray[$index] = $smcFunc['htmlspecialchars']($searchArray[$index]);
1473
		}
1474
	}
1475
	$searchArray = array_unique($searchArray);
1476
1477
	// Create an array of replacements for highlighting.
1478
	$context['mark'] = array();
1479
	foreach ($searchArray as $word)
1480
		$context['mark'][$word] = '<strong class="highlight">' . $word . '</strong>';
1481
1482
	// This contains *everything*
1483
	$searchWords = array_merge($searchArray, $excludedWords);
1484
1485
	// Make sure at least one word is being searched for.
1486
	if (empty($searchArray))
1487
		$context['search_errors']['invalid_search_string'] = true;
1488
1489
	// Sort out the search query so the user can edit it - if they want.
1490
	$context['search_params'] = $search_params;
1491
	if (isset($context['search_params']['search']))
1492
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
1493
	if (isset($context['search_params']['userspec']))
1494
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
1495
1496
	// Now we have all the parameters, combine them together for pagination and the like...
1497
	$context['params'] = array();
1498
	foreach ($search_params as $k => $v)
1499
		$context['params'][] = $k . '|\'|' . $v;
1500
	$context['params'] = base64_encode(implode('|"|', $context['params']));
1501
1502
	// Compile the subject query part.
1503
	$andQueryParts = array();
1504
1505
	foreach ($searchWords as $index => $word)
1506
	{
1507
		if ($word == '')
1508
			continue;
1509
1510
		if ($search_params['subject_only'])
1511
			$andQueryParts[] = 'pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '}';
1512
		else
1513
			$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 . '})';
1514
		$searchq_parameters['search_' . $index] = '%' . strtr($word, array('_' => '\\_', '%' => '\\%')) . '%';
1515
	}
1516
1517
	$searchQuery = ' 1=1';
1518
	if (!empty($andQueryParts))
1519
		$searchQuery = implode(!empty($search_params['searchtype']) && $search_params['searchtype'] == 2 ? ' OR ' : ' AND ', $andQueryParts);
1520
1521
	// Age limits?
1522
	$timeQuery = '';
1523
	if (!empty($search_params['minage']))
1524
		$timeQuery .= ' AND pm.msgtime < ' . (time() - $search_params['minage'] * 86400);
1525
	if (!empty($search_params['maxage']))
1526
		$timeQuery .= ' AND pm.msgtime > ' . (time() - $search_params['maxage'] * 86400);
1527
1528
	// If we have errors - return back to the first screen...
1529
	if (!empty($context['search_errors']))
1530
	{
1531
		$_REQUEST['params'] = $context['params'];
1532
		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...
1533
	}
1534
1535
	// Get the amount of results.
1536
	$request = $smcFunc['db_query']('', '
1537
		SELECT COUNT(*)
1538
		FROM {db_prefix}pm_recipients AS pmr
1539
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1540
			' . $labelJoin . '
1541
		WHERE ' . ($context['folder'] == 'inbox' ? '
1542
			pmr.id_member = {int:current_member}
1543
			AND pmr.deleted = {int:not_deleted}' : '
1544
			pm.id_member_from = {int:current_member}
1545
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1546
			' . $userQuery . $labelQuery . $timeQuery . '
1547
			AND (' . $searchQuery . ')',
1548
		array_merge($searchq_parameters, array(
1549
			'current_member' => $user_info['id'],
1550
			'not_deleted' => 0,
1551
		))
1552
	);
1553
	list ($numResults) = $smcFunc['db_fetch_row']($request);
1554
	$smcFunc['db_free_result']($request);
1555
1556
	// Get all the matching messages... using standard search only (No caching and the like!)
1557
	// @todo This doesn't support sent item searching yet.
1558
	$request = $smcFunc['db_query']('', '
1559
		SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
1560
		FROM {db_prefix}pm_recipients AS pmr
1561
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1562
			' . $labelJoin . '
1563
		WHERE ' . ($context['folder'] == 'inbox' ? '
1564
			pmr.id_member = {int:current_member}
1565
			AND pmr.deleted = {int:not_deleted}' : '
1566
			pm.id_member_from = {int:current_member}
1567
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1568
			' . $userQuery . $labelQuery . $timeQuery . '
1569
			AND (' . $searchQuery . ')
1570
		ORDER BY {raw:sort} {raw:sort_dir}
1571
		LIMIT {int:start}, {int:max}',
1572
		array_merge($searchq_parameters, array(
1573
			'current_member' => $user_info['id'],
1574
			'not_deleted' => 0,
1575
			'sort' => $search_params['sort'],
1576
			'sort_dir' => $search_params['sort_dir'],
1577
			'start' => $context['start'],
1578
			'max' => $modSettings['search_results_per_page'],
1579
		))
1580
	);
1581
	$foundMessages = array();
1582
	$posters = array();
1583
	$head_pms = array();
1584
	while ($row = $smcFunc['db_fetch_assoc']($request))
1585
	{
1586
		$foundMessages[] = $row['id_pm'];
1587
		$posters[] = $row['id_member_from'];
1588
		$head_pms[$row['id_pm']] = $row['id_pm_head'];
1589
	}
1590
	$smcFunc['db_free_result']($request);
1591
1592
	// Find the real head pms!
1593
	if ($context['display_mode'] == 2 && !empty($head_pms))
1594
	{
1595
		$request = $smcFunc['db_query']('', '
1596
			SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
1597
			FROM {db_prefix}personal_messages AS pm
1598
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
1599
			WHERE pm.id_pm_head IN ({array_int:head_pms})
1600
				AND pmr.id_member = {int:current_member}
1601
				AND pmr.deleted = {int:not_deleted}
1602
			GROUP BY pm.id_pm_head
1603
			LIMIT {int:limit}',
1604
			array(
1605
				'head_pms' => array_unique($head_pms),
1606
				'current_member' => $user_info['id'],
1607
				'not_deleted' => 0,
1608
				'limit' => count($head_pms),
1609
			)
1610
		);
1611
		$real_pm_ids = array();
1612
		while ($row = $smcFunc['db_fetch_assoc']($request))
1613
			$real_pm_ids[$row['id_pm_head']] = $row['id_pm'];
1614
		$smcFunc['db_free_result']($request);
1615
	}
1616
1617
	// Load the users...
1618
	loadMemberData($posters);
1619
1620
	// Sort out the page index.
1621
	$context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=search2;params=' . $context['params'], $_GET['start'], $numResults, $modSettings['search_results_per_page'], false);
1622
1623
	$context['message_labels'] = array();
1624
	$context['message_replied'] = array();
1625
	$context['personal_messages'] = array();
1626
1627
	if (!empty($foundMessages))
1628
	{
1629
		// Now get recipients (but don't include bcc-recipients for your inbox, you're not supposed to know :P!)
1630
		$request = $smcFunc['db_query']('', '
1631
			SELECT
1632
				pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name,
1633
				pmr.bcc, pmr.in_inbox, pmr.is_read
1634
			FROM {db_prefix}pm_recipients AS pmr
1635
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
1636
			WHERE pmr.id_pm IN ({array_int:message_list})',
1637
			array(
1638
				'message_list' => $foundMessages,
1639
			)
1640
		);
1641
		while ($row = $smcFunc['db_fetch_assoc']($request))
1642
		{
1643
			if ($context['folder'] == 'sent' || empty($row['bcc']))
1644
				$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>';
1645
1646
			if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
1647
			{
1648
				$context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
1649
1650
				$row['labels'] = '';
1651
1652
				// Get the labels for this PM
1653
				$request2 = $smcFunc['db_query']('', '
1654
					SELECT id_label
1655
					FROM {db_prefix}pm_labeled_messages
1656
					WHERE id_pm = {int:current_pm}',
1657
					array(
1658
						'current_pm' => $row['id_pm'],
1659
					)
1660
				);
1661
1662
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
1663
				{
1664
					$l_id = $row2['id_label'];
1665
					if (isset($context['labels'][$l_id]))
1666
						$context['message_labels'][$row['id_pm']][$l_id] = array('id' => $l_id, 'name' => $context['labels'][$l_id]['name']);
1667
1668
					// Here we find the first label on a message - for linking to posts in results
1669
					if (!isset($context['first_label'][$row['id_pm']]) && $row['in_inbox'] != 1)
1670
						$context['first_label'][$row['id_pm']] = $l_id;
1671
				}
1672
1673
				$smcFunc['db_free_result']($request2);
1674
1675
				// Is this in the inbox as well?
1676
				if ($row['in_inbox'] == 1)
1677
				{
1678
					$context['message_labels'][$row['id_pm']][-1] = array('id' => -1, 'name' => $context['labels'][-1]['name']);
1679
				}
1680
1681
				$row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']);
1682
			}
1683
		}
1684
1685
		// Prepare the query for the callback!
1686
		$request = $smcFunc['db_query']('', '
1687
			SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
1688
			FROM {db_prefix}personal_messages AS pm
1689
			WHERE pm.id_pm IN ({array_int:message_list})
1690
			ORDER BY {raw:sort} {raw:sort_dir}
1691
			LIMIT {int:limit}',
1692
			array(
1693
				'message_list' => $foundMessages,
1694
				'limit' => count($foundMessages),
1695
				'sort' => $search_params['sort'],
1696
				'sort_dir' => $search_params['sort_dir'],
1697
			)
1698
		);
1699
		$counter = 0;
1700
		while ($row = $smcFunc['db_fetch_assoc']($request))
1701
		{
1702
			// If there's no message subject, use the default.
1703
			$row['subject'] = $row['subject'] == '' ? $txt['no_subject'] : $row['subject'];
1704
1705
			// Load this posters context info, if it ain't there then fill in the essentials...
1706
			if (!loadMemberContext($row['id_member_from'], true))
1707
			{
1708
				$memberContext[$row['id_member_from']]['name'] = $row['from_name'];
1709
				$memberContext[$row['id_member_from']]['id'] = 0;
1710
				$memberContext[$row['id_member_from']]['group'] = $txt['guest_title'];
1711
				$memberContext[$row['id_member_from']]['link'] = $row['from_name'];
1712
				$memberContext[$row['id_member_from']]['email'] = '';
1713
				$memberContext[$row['id_member_from']]['is_guest'] = true;
1714
			}
1715
1716
			// Censor anything we don't want to see...
1717
			censorText($row['body']);
1718
			censorText($row['subject']);
1719
1720
			// Parse out any BBC...
1721
			$row['body'] = parse_bbc($row['body'], true, 'pm' . $row['id_pm']);
1722
1723
			$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'];
1724
			$context['personal_messages'][] = array(
1725
				'id' => $row['id_pm'],
1726
				'member' => &$memberContext[$row['id_member_from']],
1727
				'subject' => $row['subject'],
1728
				'body' => $row['body'],
1729
				'time' => timeformat($row['msgtime']),
1730
				'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...
1731
				'labels' => &$context['message_labels'][$row['id_pm']],
1732
				'fully_labeled' => count($context['message_labels'][$row['id_pm']]) == count($context['labels']),
1733
				'is_replied_to' => &$context['message_replied'][$row['id_pm']],
1734
				'href' => $href,
1735
				'link' => '<a href="' . $href . '">' . $row['subject'] . '</a>',
1736
				'counter' => ++$counter,
1737
			);
1738
		}
1739
		$smcFunc['db_free_result']($request);
1740
	}
1741
1742
	call_integration_hook('integrate_search_pm_context');
1743
1744
	// Finish off the context.
1745
	$context['page_title'] = $txt['pm_search_title'];
1746
	$context['sub_template'] = 'search_results';
1747
	$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'search';
1748
	$context['linktree'][] = array(
1749
		'url' => $scripturl . '?action=pm;sa=search',
1750
		'name' => $txt['pm_search_bar_title'],
1751
	);
1752
}
1753
1754
/**
1755
 * Send a new message?
1756
 */
1757
function MessagePost()
1758
{
1759
	global $txt, $sourcedir, $scripturl, $modSettings;
1760
	global $context, $smcFunc, $language, $user_info;
1761
1762
	isAllowedTo('pm_send');
1763
1764
	loadLanguage('PersonalMessage');
1765
	// Just in case it was loaded from somewhere else.
1766
	loadTemplate('PersonalMessage');
1767
	loadJavaScriptFile('PersonalMessage.js', array('defer' => false, 'minimize' => true), 'smf_pms');
1768
	loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
1769
	$context['sub_template'] = 'send';
1770
1771
	// Extract out the spam settings - cause it's neat.
1772
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
1773
1774
	// Set the title...
1775
	$context['page_title'] = $txt['send_message'];
1776
1777
	$context['reply'] = isset($_REQUEST['pmsg']) || isset($_REQUEST['quote']);
1778
1779
	// Check whether we've gone over the limit of messages we can send per hour.
1780
	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')
1781
	{
1782
		// How many messages have they sent this last hour?
1783
		$request = $smcFunc['db_query']('', '
1784
			SELECT COUNT(pr.id_pm) AS post_count
1785
			FROM {db_prefix}personal_messages AS pm
1786
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
1787
			WHERE pm.id_member_from = {int:current_member}
1788
				AND pm.msgtime > {int:msgtime}',
1789
			array(
1790
				'current_member' => $user_info['id'],
1791
				'msgtime' => time() - 3600,
1792
			)
1793
		);
1794
		list ($postCount) = $smcFunc['db_fetch_row']($request);
1795
		$smcFunc['db_free_result']($request);
1796
1797
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
1798
			fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
1799
	}
1800
1801
	// Quoting/Replying to a message?
1802
	if (!empty($_REQUEST['pmsg']))
1803
	{
1804
		$pmsg = (int) $_REQUEST['pmsg'];
1805
1806
		// Make sure this is yours.
1807
		if (!isAccessiblePM($pmsg))
1808
			fatal_lang_error('no_access', false);
1809
1810
		// Work out whether this is one you've received?
1811
		$request = $smcFunc['db_query']('', '
1812
			SELECT
1813
				id_pm
1814
			FROM {db_prefix}pm_recipients
1815
			WHERE id_pm = {int:id_pm}
1816
				AND id_member = {int:current_member}
1817
			LIMIT 1',
1818
			array(
1819
				'current_member' => $user_info['id'],
1820
				'id_pm' => $pmsg,
1821
			)
1822
		);
1823
		$isReceived = $smcFunc['db_num_rows']($request) != 0;
1824
		$smcFunc['db_free_result']($request);
1825
1826
		// Get the quoted message (and make sure you're allowed to see this quote!).
1827
		$request = $smcFunc['db_query']('', '
1828
			SELECT
1829
				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,
1830
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
1831
				COALESCE(mem.real_name, pm.from_name) AS real_name
1832
			FROM {db_prefix}personal_messages AS pm' . (!$isReceived ? '' : '
1833
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:id_pm})') . '
1834
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
1835
			WHERE pm.id_pm = {int:id_pm}' . (!$isReceived ? '
1836
				AND pm.id_member_from = {int:current_member}' : '
1837
				AND pmr.id_member = {int:current_member}') . '
1838
			LIMIT 1',
1839
			array(
1840
				'current_member' => $user_info['id'],
1841
				'id_pm_head_empty' => 0,
1842
				'id_pm' => $pmsg,
1843
			)
1844
		);
1845
		if ($smcFunc['db_num_rows']($request) == 0)
1846
			fatal_lang_error('pm_not_yours', false);
1847
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
1848
		$smcFunc['db_free_result']($request);
1849
1850
		// Censor the message.
1851
		censorText($row_quoted['subject']);
1852
		censorText($row_quoted['body']);
1853
1854
		// Add 'Re: ' to it....
1855
		if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix')))
1856
		{
1857
			if ($language === $user_info['language'])
1858
				$context['response_prefix'] = $txt['response_prefix'];
1859
			else
1860
			{
1861
				loadLanguage('index', $language, false);
1862
				$context['response_prefix'] = $txt['response_prefix'];
1863
				loadLanguage('index');
1864
			}
1865
			cache_put_data('response_prefix', $context['response_prefix'], 600);
1866
		}
1867
		$form_subject = $row_quoted['subject'];
1868
		if ($context['reply'] && trim($context['response_prefix']) != '' && $smcFunc['strpos']($form_subject, trim($context['response_prefix'])) !== 0)
1869
			$form_subject = $context['response_prefix'] . $form_subject;
1870
1871
		if (isset($_REQUEST['quote']))
1872
		{
1873
			// Remove any nested quotes and <br>...
1874
			$form_message = preg_replace('~<br ?/?' . '>~i', "\n", $row_quoted['body']);
1875
			if (!empty($modSettings['removeNestedQuotes']))
1876
				$form_message = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $form_message);
1877
			if (empty($row_quoted['id_member']))
1878
				$form_message = '[quote author=&quot;' . $row_quoted['real_name'] . '&quot;]' . "\n" . $form_message . "\n" . '[/quote]';
1879
			else
1880
				$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]';
1881
		}
1882
		else
1883
			$form_message = '';
1884
1885
		// Do the BBC thang on the message.
1886
		$row_quoted['body'] = parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']);
1887
1888
		// Set up the quoted message array.
1889
		$context['quoted_message'] = array(
1890
			'id' => $row_quoted['id_pm'],
1891
			'pm_head' => $row_quoted['pm_head'],
1892
			'member' => array(
1893
				'name' => $row_quoted['real_name'],
1894
				'username' => $row_quoted['member_name'],
1895
				'id' => $row_quoted['id_member'],
1896
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
1897
				'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'],
1898
			),
1899
			'subject' => $row_quoted['subject'],
1900
			'time' => timeformat($row_quoted['msgtime']),
1901
			'timestamp' => forum_time(true, $row_quoted['msgtime']),
1902
			'body' => $row_quoted['body']
1903
		);
1904
	}
1905
	else
1906
	{
1907
		$context['quoted_message'] = false;
1908
		$form_subject = '';
1909
		$form_message = '';
1910
	}
1911
1912
	$context['recipients'] = array(
1913
		'to' => array(),
1914
		'bcc' => array(),
1915
	);
1916
1917
	// Sending by ID?  Replying to all?  Fetch the real_name(s).
1918
	if (isset($_REQUEST['u']))
1919
	{
1920
		// If the user is replying to all, get all the other members this was sent to..
1921
		if ($_REQUEST['u'] == 'all' && isset($row_quoted))
1922
		{
1923
			// Firstly, to reply to all we clearly already have $row_quoted - so have the original member from.
1924
			if ($row_quoted['id_member'] != $user_info['id'])
1925
				$context['recipients']['to'][] = array(
1926
					'id' => $row_quoted['id_member'],
1927
					'name' => $smcFunc['htmlspecialchars']($row_quoted['real_name']),
1928
				);
1929
1930
			// Now to get the others.
1931
			$request = $smcFunc['db_query']('', '
1932
				SELECT mem.id_member, mem.real_name
1933
				FROM {db_prefix}pm_recipients AS pmr
1934
					INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
1935
				WHERE pmr.id_pm = {int:id_pm}
1936
					AND pmr.id_member != {int:current_member}
1937
					AND pmr.bcc = {int:not_bcc}',
1938
				array(
1939
					'current_member' => $user_info['id'],
1940
					'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...
1941
					'not_bcc' => 0,
1942
				)
1943
			);
1944
			while ($row = $smcFunc['db_fetch_assoc']($request))
1945
				$context['recipients']['to'][] = array(
1946
					'id' => $row['id_member'],
1947
					'name' => $row['real_name'],
1948
				);
1949
			$smcFunc['db_free_result']($request);
1950
		}
1951
		else
1952
		{
1953
			$_REQUEST['u'] = explode(',', $_REQUEST['u']);
1954
			foreach ($_REQUEST['u'] as $key => $uID)
1955
				$_REQUEST['u'][$key] = (int) $uID;
1956
1957
			$_REQUEST['u'] = array_unique($_REQUEST['u']);
1958
1959
			$request = $smcFunc['db_query']('', '
1960
				SELECT id_member, real_name
1961
				FROM {db_prefix}members
1962
				WHERE id_member IN ({array_int:member_list})
1963
				LIMIT {int:limit}',
1964
				array(
1965
					'member_list' => $_REQUEST['u'],
1966
					'limit' => count($_REQUEST['u']),
1967
				)
1968
			);
1969
			while ($row = $smcFunc['db_fetch_assoc']($request))
1970
				$context['recipients']['to'][] = array(
1971
					'id' => $row['id_member'],
1972
					'name' => $row['real_name'],
1973
				);
1974
			$smcFunc['db_free_result']($request);
1975
		}
1976
1977
		// Get a literal name list in case the user has JavaScript disabled.
1978
		$names = array();
1979
		foreach ($context['recipients']['to'] as $to)
1980
			$names[] = $to['name'];
1981
		$context['to_value'] = empty($names) ? '' : '&quot;' . implode('&quot;, &quot;', $names) . '&quot;';
1982
	}
1983
	else
1984
		$context['to_value'] = '';
1985
1986
	// Set the defaults...
1987
	$context['subject'] = $form_subject;
1988
	$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
1989
	$context['post_error'] = array();
1990
1991
	// And build the link tree.
1992
	$context['linktree'][] = array(
1993
		'url' => $scripturl . '?action=pm;sa=send',
1994
		'name' => $txt['new_message']
1995
	);
1996
1997
	$modSettings['disable_wysiwyg'] = !empty($modSettings['disable_wysiwyg']) || empty($modSettings['enableBBC']);
1998
1999
	// Generate a list of drafts that they can load in to the editor
2000
	if (!empty($context['drafts_pm_save']))
2001
	{
2002
		require_once($sourcedir . '/Drafts.php');
2003
		$pm_seed = isset($_REQUEST['pmsg']) ? $_REQUEST['pmsg'] : (isset($_REQUEST['quote']) ? $_REQUEST['quote'] : 0);
2004
		ShowDrafts($user_info['id'], $pm_seed, 1);
2005
	}
2006
2007
	// Needed for the WYSIWYG editor.
2008
	require_once($sourcedir . '/Subs-Editor.php');
2009
2010
	// Now create the editor.
2011
	$editorOptions = array(
2012
		'id' => 'message',
2013
		'value' => $context['message'],
2014
		'height' => '250px',
2015
		'width' => '100%',
2016
		'labels' => array(
2017
			'post_button' => $txt['send_message'],
2018
		),
2019
		'preview_type' => 2,
2020
		'required' => true,
2021
	);
2022
	create_control_richedit($editorOptions);
2023
2024
	// Store the ID for old compatibility.
2025
	$context['post_box_name'] = $editorOptions['id'];
2026
2027
	$context['bcc_value'] = '';
2028
2029
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
2030
	if ($context['require_verification'])
2031
	{
2032
		$verificationOptions = array(
2033
			'id' => 'pm',
2034
		);
2035
		$context['require_verification'] = create_control_verification($verificationOptions);
2036
		$context['visual_verification_id'] = $verificationOptions['id'];
2037
	}
2038
2039
	call_integration_hook('integrate_pm_post');
2040
2041
	// Register this form and get a sequence number in $context.
2042
	checkSubmitOnce('register');
2043
}
2044
2045
/**
2046
 * This function allows the user to view their PM drafts
2047
 */
2048
function MessageDrafts()
2049
{
2050
	global $sourcedir, $user_info;
2051
2052
	// validate with loadMemberData()
2053
	$memberResult = loadMemberData($user_info['id'], false);
2054
	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...
2055
		fatal_lang_error('not_a_user', false);
2056
	list ($memID) = $memberResult;
2057
2058
	// drafts is where the functions reside
2059
	require_once($sourcedir . '/Drafts.php');
2060
	showPMDrafts($memID);
2061
}
2062
2063
/**
2064
 * An error in the message...
2065
 *
2066
 * @param array $error_types An array of strings indicating which type of errors occurred
2067
 * @param array $named_recipients
2068
 * @param $recipient_ids
2069
 */
2070
function messagePostError($error_types, $named_recipients, $recipient_ids = array())
2071
{
2072
	global $txt, $context, $scripturl, $modSettings;
2073
	global $smcFunc, $user_info, $sourcedir;
2074
2075
	if (!isset($_REQUEST['xml']))
2076
	{
2077
		$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'send';
2078
		$context['sub_template'] = 'send';
2079
		loadJavaScriptFile('PersonalMessage.js', array('defer' => false, 'minimize' => true), 'smf_pms');
2080
		loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
2081
	}
2082
	else
2083
		$context['sub_template'] = 'pm';
2084
2085
	$context['page_title'] = $txt['send_message'];
2086
2087
	// Got some known members?
2088
	$context['recipients'] = array(
2089
		'to' => array(),
2090
		'bcc' => array(),
2091
	);
2092
	if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc']))
2093
	{
2094
		$allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']);
2095
2096
		$request = $smcFunc['db_query']('', '
2097
			SELECT id_member, real_name
2098
			FROM {db_prefix}members
2099
			WHERE id_member IN ({array_int:member_list})',
2100
			array(
2101
				'member_list' => $allRecipients,
2102
			)
2103
		);
2104
		while ($row = $smcFunc['db_fetch_assoc']($request))
2105
		{
2106
			$recipientType = in_array($row['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to';
2107
			$context['recipients'][$recipientType][] = array(
2108
				'id' => $row['id_member'],
2109
				'name' => $row['real_name'],
2110
			);
2111
		}
2112
		$smcFunc['db_free_result']($request);
2113
	}
2114
2115
	// Set everything up like before....
2116
	$context['subject'] = isset($_REQUEST['subject']) ? $smcFunc['htmlspecialchars']($_REQUEST['subject']) : '';
2117
	$context['message'] = isset($_REQUEST['message']) ? str_replace(array('  '), array('&nbsp; '), $smcFunc['htmlspecialchars']($_REQUEST['message'])) : '';
2118
	$context['reply'] = !empty($_REQUEST['replied_to']);
2119
2120
	if ($context['reply'])
2121
	{
2122
		$_REQUEST['replied_to'] = (int) $_REQUEST['replied_to'];
2123
2124
		$request = $smcFunc['db_query']('', '
2125
			SELECT
2126
				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,
2127
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
2128
				COALESCE(mem.real_name, pm.from_name) AS real_name
2129
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' : '
2130
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:replied_to})') . '
2131
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
2132
			WHERE pm.id_pm = {int:replied_to}' . ($context['folder'] == 'sent' ? '
2133
				AND pm.id_member_from = {int:current_member}' : '
2134
				AND pmr.id_member = {int:current_member}') . '
2135
			LIMIT 1',
2136
			array(
2137
				'current_member' => $user_info['id'],
2138
				'no_id_pm_head' => 0,
2139
				'replied_to' => $_REQUEST['replied_to'],
2140
			)
2141
		);
2142
		if ($smcFunc['db_num_rows']($request) == 0)
2143
		{
2144
			if (!isset($_REQUEST['xml']))
2145
				fatal_lang_error('pm_not_yours', false);
2146
			else
2147
				$error_types[] = 'pm_not_yours';
2148
		}
2149
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
2150
		$smcFunc['db_free_result']($request);
2151
2152
		censorText($row_quoted['subject']);
2153
		censorText($row_quoted['body']);
2154
2155
		$context['quoted_message'] = array(
2156
			'id' => $row_quoted['id_pm'],
2157
			'pm_head' => $row_quoted['pm_head'],
2158
			'member' => array(
2159
				'name' => $row_quoted['real_name'],
2160
				'username' => $row_quoted['member_name'],
2161
				'id' => $row_quoted['id_member'],
2162
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
2163
				'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'],
2164
			),
2165
			'subject' => $row_quoted['subject'],
2166
			'time' => timeformat($row_quoted['msgtime']),
2167
			'timestamp' => forum_time(true, $row_quoted['msgtime']),
2168
			'body' => parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']),
2169
		);
2170
	}
2171
2172
	// Build the link tree....
2173
	$context['linktree'][] = array(
2174
		'url' => $scripturl . '?action=pm;sa=send',
2175
		'name' => $txt['new_message']
2176
	);
2177
2178
	// Set each of the errors for the template.
2179
	loadLanguage('Errors');
2180
2181
	$context['error_type'] = 'minor';
2182
2183
	$context['post_error'] = array(
2184
		'messages' => array(),
2185
		// @todo error handling: maybe fatal errors can be error_type => serious
2186
		'error_type' => '',
2187
	);
2188
2189
	foreach ($error_types as $error_type)
2190
	{
2191
		$context['post_error'][$error_type] = true;
2192
		if (isset($txt['error_' . $error_type]))
2193
		{
2194
			if ($error_type == 'long_message')
2195
				$txt['error_' . $error_type] = sprintf($txt['error_' . $error_type], $modSettings['max_messageLength']);
2196
2197
			$context['post_error']['messages'][] = $txt['error_' . $error_type];
2198
		}
2199
2200
		// If it's not a minor error flag it as such.
2201
		if (!in_array($error_type, array('new_reply', 'not_approved', 'new_replies', 'old_topic', 'need_qr_verification', 'no_subject')))
2202
			$context['error_type'] = 'serious';
2203
	}
2204
2205
	// We need to load the editor once more.
2206
	require_once($sourcedir . '/Subs-Editor.php');
2207
2208
	// Create it...
2209
	$editorOptions = array(
2210
		'id' => 'message',
2211
		'value' => $context['message'],
2212
		'width' => '90%',
2213
		'height' => '250px',
2214
		'labels' => array(
2215
			'post_button' => $txt['send_message'],
2216
		),
2217
		'preview_type' => 2,
2218
	);
2219
	create_control_richedit($editorOptions);
2220
2221
	// ... and store the ID again...
2222
	$context['post_box_name'] = $editorOptions['id'];
2223
2224
	// Check whether we need to show the code again.
2225
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
2226
	if ($context['require_verification'] && !isset($_REQUEST['xml']))
2227
	{
2228
		require_once($sourcedir . '/Subs-Editor.php');
2229
		$verificationOptions = array(
2230
			'id' => 'pm',
2231
		);
2232
		$context['require_verification'] = create_control_verification($verificationOptions);
2233
		$context['visual_verification_id'] = $verificationOptions['id'];
2234
	}
2235
2236
	$context['to_value'] = empty($named_recipients['to']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['to']) . '&quot;';
2237
	$context['bcc_value'] = empty($named_recipients['bcc']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['bcc']) . '&quot;';
2238
2239
	call_integration_hook('integrate_pm_error');
2240
2241
	// No check for the previous submission is needed.
2242
	checkSubmitOnce('free');
2243
2244
	// Acquire a new form sequence number.
2245
	checkSubmitOnce('register');
2246
}
2247
2248
/**
2249
 * Send it!
2250
 */
2251
function MessagePost2()
2252
{
2253
	global $txt, $context, $sourcedir;
2254
	global $user_info, $modSettings, $smcFunc;
2255
2256
	isAllowedTo('pm_send');
2257
	require_once($sourcedir . '/Subs-Auth.php');
2258
2259
	// PM Drafts enabled and needed?
2260
	if ($context['drafts_pm_save'] && (isset($_POST['save_draft']) || isset($_POST['id_pm_draft'])))
2261
		require_once($sourcedir . '/Drafts.php');
2262
2263
	loadLanguage('PersonalMessage', '', false);
2264
2265
	// Extract out the spam settings - it saves database space!
2266
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
2267
2268
	// Initialize the errors we're about to make.
2269
	$post_errors = array();
2270
2271
	// Check whether we've gone over the limit of messages we can send per hour - fatal error if fails!
2272
	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')
2273
	{
2274
		// How many have they sent this last hour?
2275
		$request = $smcFunc['db_query']('', '
2276
			SELECT COUNT(pr.id_pm) AS post_count
2277
			FROM {db_prefix}personal_messages AS pm
2278
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
2279
			WHERE pm.id_member_from = {int:current_member}
2280
				AND pm.msgtime > {int:msgtime}',
2281
			array(
2282
				'current_member' => $user_info['id'],
2283
				'msgtime' => time() - 3600,
2284
			)
2285
		);
2286
		list ($postCount) = $smcFunc['db_fetch_row']($request);
2287
		$smcFunc['db_free_result']($request);
2288
2289
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
2290
		{
2291
			if (!isset($_REQUEST['xml']))
2292
				fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
2293
			else
2294
				$post_errors[] = 'pm_too_many_per_hour';
2295
		}
2296
	}
2297
2298
	// If your session timed out, show an error, but do allow to re-submit.
2299
	if (!isset($_REQUEST['xml']) && checkSession('post', '', false) != '')
2300
		$post_errors[] = 'session_timeout';
2301
2302
	$_REQUEST['subject'] = isset($_REQUEST['subject']) ? trim($_REQUEST['subject']) : '';
2303
	$_REQUEST['to'] = empty($_POST['to']) ? (empty($_GET['to']) ? '' : $_GET['to']) : $_POST['to'];
2304
	$_REQUEST['bcc'] = empty($_POST['bcc']) ? (empty($_GET['bcc']) ? '' : $_GET['bcc']) : $_POST['bcc'];
2305
2306
	// Route the input from the 'u' parameter to the 'to'-list.
2307
	if (!empty($_POST['u']))
2308
		$_POST['recipient_to'] = explode(',', $_POST['u']);
2309
2310
	// Construct the list of recipients.
2311
	$recipientList = array();
2312
	$namedRecipientList = array();
2313
	$namesNotFound = array();
2314
	foreach (array('to', 'bcc') as $recipientType)
2315
	{
2316
		// First, let's see if there's user ID's given.
2317
		$recipientList[$recipientType] = array();
2318
		if (!empty($_POST['recipient_' . $recipientType]) && is_array($_POST['recipient_' . $recipientType]))
2319
		{
2320
			foreach ($_POST['recipient_' . $recipientType] as $recipient)
2321
				$recipientList[$recipientType][] = (int) $recipient;
2322
		}
2323
2324
		// Are there also literal names set?
2325
		if (!empty($_REQUEST[$recipientType]))
2326
		{
2327
			// We're going to take out the "s anyway ;).
2328
			$recipientString = strtr($_REQUEST[$recipientType], array('\\"' => '"'));
2329
2330
			preg_match_all('~"([^"]+)"~', $recipientString, $matches);
2331
			$namedRecipientList[$recipientType] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))));
2332
2333
			foreach ($namedRecipientList[$recipientType] as $index => $recipient)
2334
			{
2335
				if (strlen(trim($recipient)) > 0)
2336
					$namedRecipientList[$recipientType][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($recipient)));
2337
				else
2338
					unset($namedRecipientList[$recipientType][$index]);
2339
			}
2340
2341
			if (!empty($namedRecipientList[$recipientType]))
2342
			{
2343
				$foundMembers = findMembers($namedRecipientList[$recipientType]);
2344
2345
				// Assume all are not found, until proven otherwise.
2346
				$namesNotFound[$recipientType] = $namedRecipientList[$recipientType];
2347
2348
				foreach ($foundMembers as $member)
2349
				{
2350
					$testNames = array(
2351
						$smcFunc['strtolower']($member['username']),
2352
						$smcFunc['strtolower']($member['name']),
2353
						$smcFunc['strtolower']($member['email']),
2354
					);
2355
2356
					if (count(array_intersect($testNames, $namedRecipientList[$recipientType])) !== 0)
2357
					{
2358
						$recipientList[$recipientType][] = $member['id'];
2359
2360
						// Get rid of this username, since we found it.
2361
						$namesNotFound[$recipientType] = array_diff($namesNotFound[$recipientType], $testNames);
2362
					}
2363
				}
2364
			}
2365
		}
2366
2367
		// Selected a recipient to be deleted? Remove them now.
2368
		if (!empty($_POST['delete_recipient']))
2369
			$recipientList[$recipientType] = array_diff($recipientList[$recipientType], array((int) $_POST['delete_recipient']));
2370
2371
		// Make sure we don't include the same name twice
2372
		$recipientList[$recipientType] = array_unique($recipientList[$recipientType]);
2373
	}
2374
2375
	// Are we changing the recipients some how?
2376
	$is_recipient_change = !empty($_POST['delete_recipient']) || !empty($_POST['to_submit']) || !empty($_POST['bcc_submit']);
2377
2378
	// Check if there's at least one recipient.
2379
	if (empty($recipientList['to']) && empty($recipientList['bcc']))
2380
		$post_errors[] = 'no_to';
2381
2382
	// Make sure that we remove the members who did get it from the screen.
2383
	if (!$is_recipient_change)
2384
	{
2385
		foreach ($recipientList as $recipientType => $dummy)
2386
		{
2387
			if (!empty($namesNotFound[$recipientType]))
2388
			{
2389
				$post_errors[] = 'bad_' . $recipientType;
2390
2391
				// Since we already have a post error, remove the previous one.
2392
				$post_errors = array_diff($post_errors, array('no_to'));
2393
2394
				foreach ($namesNotFound[$recipientType] as $name)
2395
					$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
2396
			}
2397
		}
2398
	}
2399
2400
	// Did they make any mistakes?
2401
	if ($_REQUEST['subject'] == '')
2402
		$post_errors[] = 'no_subject';
2403
	if (!isset($_REQUEST['message']) || $_REQUEST['message'] == '')
2404
		$post_errors[] = 'no_message';
2405
	elseif (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_REQUEST['message']) > $modSettings['max_messageLength'])
2406
		$post_errors[] = 'long_message';
2407
	else
2408
	{
2409
		// Preparse the message.
2410
		$message = $_REQUEST['message'];
2411
		preparsecode($message);
2412
2413
		// Make sure there's still some content left without the tags.
2414
		if ($smcFunc['htmltrim'](strip_tags(parse_bbc($smcFunc['htmlspecialchars']($message, ENT_QUOTES), false), '<img>')) === '' && (!allowedTo('bbc_html') || strpos($message, '[html]') === false))
2415
			$post_errors[] = 'no_message';
2416
	}
2417
2418
	// Wrong verification code?
2419
	if (!$user_info['is_admin'] && !isset($_REQUEST['xml']) && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'])
2420
	{
2421
		require_once($sourcedir . '/Subs-Editor.php');
2422
		$verificationOptions = array(
2423
			'id' => 'pm',
2424
		);
2425
		$context['require_verification'] = create_control_verification($verificationOptions, true);
2426
2427
		if (is_array($context['require_verification']))
2428
			$post_errors = array_merge($post_errors, $context['require_verification']);
2429
	}
2430
2431
	// If they did, give a chance to make ammends.
2432
	if (!empty($post_errors) && !$is_recipient_change && !isset($_REQUEST['preview']) && !isset($_REQUEST['xml']))
2433
		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...
2434
2435
	// Want to take a second glance before you send?
2436
	if (isset($_REQUEST['preview']))
2437
	{
2438
		// Set everything up to be displayed.
2439
		$context['preview_subject'] = $smcFunc['htmlspecialchars']($_REQUEST['subject']);
2440
		$context['preview_message'] = $smcFunc['htmlspecialchars']($_REQUEST['message'], ENT_QUOTES);
2441
		preparsecode($context['preview_message'], true);
2442
2443
		// Parse out the BBC if it is enabled.
2444
		$context['preview_message'] = parse_bbc($context['preview_message']);
2445
2446
		// Censor, as always.
2447
		censorText($context['preview_subject']);
2448
		censorText($context['preview_message']);
2449
2450
		// Set a descriptive title.
2451
		$context['page_title'] = $txt['preview'] . ' - ' . $context['preview_subject'];
2452
2453
		// Pretend they messed up but don't ignore if they really did :P.
2454
		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...
2455
	}
2456
2457
	// Adding a recipient cause javascript ain't working?
2458
	elseif ($is_recipient_change)
2459
	{
2460
		// Maybe we couldn't find one?
2461
		foreach ($namesNotFound as $recipientType => $names)
2462
		{
2463
			$post_errors[] = 'bad_' . $recipientType;
2464
			foreach ($names as $name)
2465
				$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
2466
		}
2467
2468
		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...
2469
	}
2470
2471
	// Want to save this as a draft and think about it some more?
2472
	if ($context['drafts_pm_save'] && isset($_POST['save_draft']))
2473
	{
2474
		SavePMDraft($post_errors, $recipientList);
2475
		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...
2476
	}
2477
2478
	// Before we send the PM, let's make sure we don't have an abuse of numbers.
2479
	elseif (!empty($modSettings['max_pm_recipients']) && count($recipientList['to']) + count($recipientList['bcc']) > $modSettings['max_pm_recipients'] && !allowedTo(array('moderate_forum', 'send_mail', 'admin_forum')))
2480
	{
2481
		$context['send_log'] = array(
2482
			'sent' => array(),
2483
			'failed' => array(sprintf($txt['pm_too_many_recipients'], $modSettings['max_pm_recipients'])),
2484
		);
2485
		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...
2486
	}
2487
2488
	// Protect from message spamming.
2489
	spamProtection('pm');
2490
2491
	// Prevent double submission of this form.
2492
	checkSubmitOnce('check');
2493
2494
	// Do the actual sending of the PM.
2495
	if (!empty($recipientList['to']) || !empty($recipientList['bcc']))
2496
		$context['send_log'] = sendpm($recipientList, $_REQUEST['subject'], $_REQUEST['message'], true, null, !empty($_REQUEST['pm_head']) ? (int) $_REQUEST['pm_head'] : 0);
2497
	else
2498
		$context['send_log'] = array(
2499
			'sent' => array(),
2500
			'failed' => array()
2501
		);
2502
2503
	// Mark the message as "replied to".
2504
	if (!empty($context['send_log']['sent']) && !empty($_REQUEST['replied_to']) && isset($_REQUEST['f']) && $_REQUEST['f'] == 'inbox')
2505
	{
2506
		$smcFunc['db_query']('', '
2507
			UPDATE {db_prefix}pm_recipients
2508
			SET is_read = is_read | 2
2509
			WHERE id_pm = {int:replied_to}
2510
				AND id_member = {int:current_member}',
2511
			array(
2512
				'current_member' => $user_info['id'],
2513
				'replied_to' => (int) $_REQUEST['replied_to'],
2514
			)
2515
		);
2516
	}
2517
2518
	// If one or more of the recipient were invalid, go back to the post screen with the failed usernames.
2519
	if (!empty($context['send_log']['failed']))
2520
		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...
2521
			'to' => array_intersect($recipientList['to'], $context['send_log']['failed']),
2522
			'bcc' => array_intersect($recipientList['bcc'], $context['send_log']['failed'])
2523
		));
2524
2525
	// Message sent successfully?
2526
	if (!empty($context['send_log']) && empty($context['send_log']['failed']))
2527
	{
2528
		$context['current_label_redirect'] = $context['current_label_redirect'] . ';done=sent';
2529
2530
		// If we had a PM draft for this one, then its time to remove it since it was just sent
2531
		if ($context['drafts_pm_save'] && !empty($_POST['id_pm_draft']))
2532
			DeleteDraft($_POST['id_pm_draft']);
2533
	}
2534
2535
	// Go back to the where they sent from, if possible...
2536
	redirectexit($context['current_label_redirect']);
2537
}
2538
2539
/**
2540
 * This function performs all additional stuff...
2541
 */
2542
function MessageActionsApply()
2543
{
2544
	global $context, $user_info, $options, $smcFunc;
2545
2546
	checkSession('request');
2547
2548
	if (isset($_REQUEST['del_selected']))
2549
		$_REQUEST['pm_action'] = 'delete';
2550
2551
	if (isset($_REQUEST['pm_action']) && $_REQUEST['pm_action'] != '' && !empty($_REQUEST['pms']) && is_array($_REQUEST['pms']))
2552
	{
2553
		foreach ($_REQUEST['pms'] as $pm)
2554
			$_REQUEST['pm_actions'][(int) $pm] = $_REQUEST['pm_action'];
2555
	}
2556
2557
	if (empty($_REQUEST['pm_actions']))
2558
		redirectexit($context['current_label_redirect']);
2559
2560
	// If we are in conversation, we may need to apply this to every message in the conversation.
2561
	if ($context['display_mode'] == 2 && isset($_REQUEST['conversation']))
2562
	{
2563
		$id_pms = array();
2564
		foreach ($_REQUEST['pm_actions'] as $pm => $dummy)
2565
			$id_pms[] = (int) $pm;
2566
2567
		$request = $smcFunc['db_query']('', '
2568
			SELECT id_pm_head, id_pm
2569
			FROM {db_prefix}personal_messages
2570
			WHERE id_pm IN ({array_int:id_pms})',
2571
			array(
2572
				'id_pms' => $id_pms,
2573
			)
2574
		);
2575
		$pm_heads = array();
2576
		while ($row = $smcFunc['db_fetch_assoc']($request))
2577
			$pm_heads[$row['id_pm_head']] = $row['id_pm'];
2578
		$smcFunc['db_free_result']($request);
2579
2580
		$request = $smcFunc['db_query']('', '
2581
			SELECT id_pm, id_pm_head
2582
			FROM {db_prefix}personal_messages
2583
			WHERE id_pm_head IN ({array_int:pm_heads})',
2584
			array(
2585
				'pm_heads' => array_keys($pm_heads),
2586
			)
2587
		);
2588
		// Copy the action from the single to PM to the others.
2589
		while ($row = $smcFunc['db_fetch_assoc']($request))
2590
		{
2591
			if (isset($pm_heads[$row['id_pm_head']]) && isset($_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]]))
2592
				$_REQUEST['pm_actions'][$row['id_pm']] = $_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]];
2593
		}
2594
		$smcFunc['db_free_result']($request);
2595
	}
2596
2597
	$to_delete = array();
2598
	$to_label = array();
2599
	$label_type = array();
2600
	$labels = array();
2601
	foreach ($_REQUEST['pm_actions'] as $pm => $action)
2602
	{
2603
		if ($action === 'delete')
2604
			$to_delete[] = (int) $pm;
2605
		else
2606
		{
2607
			if (substr($action, 0, 4) == 'add_')
2608
			{
2609
				$type = 'add';
2610
				$action = substr($action, 4);
2611
			}
2612
			elseif (substr($action, 0, 4) == 'rem_')
2613
			{
2614
				$type = 'rem';
2615
				$action = substr($action, 4);
2616
			}
2617
			else
2618
				$type = 'unk';
2619
2620
			if ($action == '-1' || (int) $action > 0)
2621
			{
2622
				$to_label[(int) $pm] = (int) $action;
2623
				$label_type[(int) $pm] = $type;
2624
			}
2625
		}
2626
	}
2627
2628
	// Deleting, it looks like?
2629
	if (!empty($to_delete))
2630
		deleteMessages($to_delete, $context['display_mode'] == 2 ? null : $context['folder']);
2631
2632
	// Are we labeling anything?
2633
	if (!empty($to_label) && $context['folder'] == 'inbox')
2634
	{
2635
		// Are we dealing with conversation view? If so, get all the messages in each conversation
2636
		if ($context['display_mode'] == 2)
2637
		{
2638
			$get_pms = $smcFunc['db_query']('', '
2639
				SELECT pm.id_pm_head, pm.id_pm
2640
				FROM {db_prefix}personal_messages AS pm
2641
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
2642
				WHERE pm.id_pm_head IN ({array_int:head_pms})
2643
					AND pm.id_pm NOT IN ({array_int:head_pms})
2644
					AND pmr.id_member = {int:current_member}',
2645
				array(
2646
					'head_pms' => array_keys($to_label),
2647
					'current_member' => $user_info['id'],
2648
				)
2649
			);
2650
2651
			while ($other_pms = $smcFunc['db_fetch_assoc']($get_pms))
2652
			{
2653
				$to_label[$other_pms['id_pm']] = $to_label[$other_pms['id_pm_head']];
2654
			}
2655
2656
			$smcFunc['db_free_result']($get_pms);
2657
		}
2658
2659
		// Get information about each message...
2660
		$request = $smcFunc['db_query']('', '
2661
			SELECT id_pm, in_inbox
2662
			FROM {db_prefix}pm_recipients
2663
			WHERE id_member = {int:current_member}
2664
				AND id_pm IN ({array_int:to_label})
2665
			LIMIT ' . count($to_label),
2666
			array(
2667
				'current_member' => $user_info['id'],
2668
				'to_label' => array_keys($to_label),
2669
			)
2670
		);
2671
2672
		while ($row = $smcFunc['db_fetch_assoc']($request))
2673
		{
2674
			// Get the labels as well, but only if we're not dealing with the inbox
2675
			if ($to_label[$row['id_pm']] != '-1')
2676
			{
2677
				// The JOIN here ensures we only get labels that this user has applied to this PM
2678
				$request2 = $smcFunc['db_query']('', '
2679
					SELECT l.id_label, pml.id_pm
2680
					FROM {db_prefix}pm_labels AS l
2681
						INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2682
					WHERE l.id_member = {int:current_member}
2683
						AND pml.id_pm = {int:current_pm}',
2684
					array(
2685
						'current_member' => $user_info['id'],
2686
						'current_pm' => $row['id_pm'],
2687
					)
2688
				);
2689
2690
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
2691
				{
2692
					$labels[$row2['id_label']] = $row2['id_label'];
2693
				}
2694
2695
				$smcFunc['db_free_result']($request2);
2696
			}
2697
			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 2601. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2698
			{
2699
				// If we're removing from the inbox, see if we have at least one other label.
2700
				// This query is faster than the one above
2701
				$request2 = $smcFunc['db_query']('', '
2702
					SELECT COUNT(l.id_label)
2703
					FROM {db_prefix}pm_labels AS l
2704
						INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2705
					WHERE l.id_member = {int:current_member}
2706
						AND pml.id_pm = {int:current_pm}',
2707
					array(
2708
						'current_member' => $user_info['id'],
2709
						'current_pm' => $row['id_pm'],
2710
					)
2711
				);
2712
2713
				// How many labels do you have?
2714
				list ($num_labels) = $smcFunc['db_fetch_assoc']($request2);
2715
2716
				if ($num_labels > 0)
2717
					$context['can_remove_inbox'] = true;
2718
2719
				$smcFunc['db_free_result']($request2);
2720
			}
2721
2722
			// Use this to determine what to do later on...
2723
			$original_labels = $labels;
2724
2725
			// Ignore inbox for now - we'll deal with it later
2726
			if ($to_label[$row['id_pm']] != '-1')
2727
			{
2728
				// If this label is in the list and we're not adding it, remove it
2729
				if (array_key_exists($to_label[$row['id_pm']], $labels) && $type !== 'add')
2730
					unset($labels[$to_label[$row['id_pm']]]);
2731
				elseif ($type !== 'rem')
2732
					$labels[$to_label[$row['id_pm']]] = $to_label[$row['id_pm']];
2733
			}
2734
2735
			// Removing all labels or just removing the inbox label
2736
			if ($type == 'rem' && empty($labels))
2737
				$in_inbox = (empty($context['can_remove_inbox']) ? 1 : 0);
2738
			// Adding new labels, but removing inbox and applying new ones
2739
			elseif ($type == 'add' && !empty($options['pm_remove_inbox_label']) && !empty($labels))
2740
				$in_inbox = 0;
2741
			// Just adding it to the inbox
2742
			else
2743
				$in_inbox = 1;
2744
2745
			// Are we adding it to or removing it from the inbox?
2746
			if ($in_inbox != $row['in_inbox'])
2747
			{
2748
				$smcFunc['db_query']('', '
2749
					UPDATE {db_prefix}pm_recipients
2750
					SET in_inbox = {int:in_inbox}
2751
					WHERE id_pm = {int:id_pm}
2752
						AND id_member = {int:current_member}',
2753
					array(
2754
						'current_member' => $user_info['id'],
2755
						'id_pm' => $row['id_pm'],
2756
						'in_inbox' => $in_inbox,
2757
					)
2758
				);
2759
			}
2760
2761
			// Which labels do we not want now?
2762
			$labels_to_remove = array_diff($original_labels, $labels);
2763
2764
			// Don't apply it if it's already applied
2765
			$labels_to_apply = array_diff($labels, $original_labels);
2766
2767
			// Remove labels
2768
			if (!empty($labels_to_remove))
2769
			{
2770
				$smcFunc['db_query']('', '
2771
					DELETE FROM {db_prefix}pm_labeled_messages
2772
					WHERE id_pm = {int:current_pm}
2773
						AND id_label IN ({array_int:labels_to_remove})',
2774
					array(
2775
						'current_pm' => $row['id_pm'],
2776
						'labels_to_remove' => $labels_to_remove,
2777
					)
2778
				);
2779
			}
2780
2781
			// Add new ones
2782
			if (!empty($labels_to_apply))
2783
			{
2784
				$inserts = array();
2785
				foreach ($labels_to_apply as $label)
2786
					$inserts[] = array($row['id_pm'], $label);
2787
2788
				$smcFunc['db_insert']('',
2789
					'{db_prefix}pm_labeled_messages',
2790
					array('id_pm' => 'int', 'id_label' => 'int'),
2791
					$inserts,
2792
					array()
2793
				);
2794
			}
2795
		}
2796
		$smcFunc['db_free_result']($request);
2797
	}
2798
2799
	// Back to the folder.
2800
	$_SESSION['pm_selected'] = array_keys($to_label);
2801
	redirectexit($context['current_label_redirect'] . (count($to_label) == 1 ? '#msg' . $_SESSION['pm_selected'][0] : ''), count($to_label) == 1 && isBrowser('ie'));
2802
}
2803
2804
/**
2805
 * Delete ALL the messages!
2806
 */
2807
function MessageKillAll()
2808
{
2809
	global $context;
2810
2811
	checkSession();
2812
2813
	deleteMessages(null, null);
2814
2815
	// Done... all gone.
2816
	redirectexit($context['current_label_redirect']);
2817
}
2818
2819
/**
2820
 * This function allows the user to delete all messages older than so many days.
2821
 */
2822
function MessagePrune()
2823
{
2824
	global $txt, $context, $user_info, $scripturl, $smcFunc;
2825
2826
	// Actually delete the messages.
2827
	if (isset($_REQUEST['age']))
2828
	{
2829
		checkSession();
2830
2831
		// Calculate the time to delete before.
2832
		$deleteTime = max(0, time() - (86400 * (int) $_REQUEST['age']));
2833
2834
		// Array to store the IDs in.
2835
		$toDelete = array();
2836
2837
		// Select all the messages they have sent older than $deleteTime.
2838
		$request = $smcFunc['db_query']('', '
2839
			SELECT id_pm
2840
			FROM {db_prefix}personal_messages
2841
			WHERE deleted_by_sender = {int:not_deleted}
2842
				AND id_member_from = {int:current_member}
2843
				AND msgtime < {int:msgtime}',
2844
			array(
2845
				'current_member' => $user_info['id'],
2846
				'not_deleted' => 0,
2847
				'msgtime' => $deleteTime,
2848
			)
2849
		);
2850
		while ($row = $smcFunc['db_fetch_row']($request))
2851
			$toDelete[] = $row[0];
2852
		$smcFunc['db_free_result']($request);
2853
2854
		// Select all messages in their inbox older than $deleteTime.
2855
		$request = $smcFunc['db_query']('', '
2856
			SELECT pmr.id_pm
2857
			FROM {db_prefix}pm_recipients AS pmr
2858
				INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
2859
			WHERE pmr.deleted = {int:not_deleted}
2860
				AND pmr.id_member = {int:current_member}
2861
				AND pm.msgtime < {int:msgtime}',
2862
			array(
2863
				'current_member' => $user_info['id'],
2864
				'not_deleted' => 0,
2865
				'msgtime' => $deleteTime,
2866
			)
2867
		);
2868
		while ($row = $smcFunc['db_fetch_assoc']($request))
2869
			$toDelete[] = $row['id_pm'];
2870
		$smcFunc['db_free_result']($request);
2871
2872
		// Delete the actual messages.
2873
		deleteMessages($toDelete);
2874
2875
		// Go back to their inbox.
2876
		redirectexit($context['current_label_redirect']);
2877
	}
2878
2879
	// Build the link tree elements.
2880
	$context['linktree'][] = array(
2881
		'url' => $scripturl . '?action=pm;sa=prune',
2882
		'name' => $txt['pm_prune']
2883
	);
2884
2885
	$context['sub_template'] = 'prune';
2886
	$context['page_title'] = $txt['pm_prune'];
2887
}
2888
2889
/**
2890
 * Delete the specified personal messages.
2891
 *
2892
 * @param array|null $personal_messages An array containing the IDs of PMs to delete or null to delete all of them
2893
 * @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
2894
 * @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
2895
 */
2896
function deleteMessages($personal_messages, $folder = null, $owner = null)
2897
{
2898
	global $user_info, $smcFunc;
2899
2900
	if ($owner === null)
2901
		$owner = array($user_info['id']);
2902
	elseif (empty($owner))
2903
		return;
2904
	elseif (!is_array($owner))
2905
		$owner = array($owner);
2906
2907
	if ($personal_messages !== null)
2908
	{
2909
		if (empty($personal_messages) || !is_array($personal_messages))
2910
			return;
2911
2912
		foreach ($personal_messages as $index => $delete_id)
2913
			$personal_messages[$index] = (int) $delete_id;
2914
2915
		$where = '
2916
				AND id_pm IN ({array_int:pm_list})';
2917
	}
2918
	else
2919
		$where = '';
2920
2921
	if ($folder == 'sent' || $folder === null)
2922
	{
2923
		$smcFunc['db_query']('', '
2924
			UPDATE {db_prefix}personal_messages
2925
			SET deleted_by_sender = {int:is_deleted}
2926
			WHERE id_member_from IN ({array_int:member_list})
2927
				AND deleted_by_sender = {int:not_deleted}' . $where,
2928
			array(
2929
				'member_list' => $owner,
2930
				'is_deleted' => 1,
2931
				'not_deleted' => 0,
2932
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2933
			)
2934
		);
2935
	}
2936
	if ($folder != 'sent' || $folder === null)
2937
	{
2938
		// Calculate the number of messages each member's gonna lose...
2939
		$request = $smcFunc['db_query']('', '
2940
			SELECT id_member, COUNT(*) AS num_deleted_messages, CASE WHEN is_read & 1 >= 1 THEN 1 ELSE 0 END AS is_read
2941
			FROM {db_prefix}pm_recipients
2942
			WHERE id_member IN ({array_int:member_list})
2943
				AND deleted = {int:not_deleted}' . $where . '
2944
			GROUP BY id_member, is_read',
2945
			array(
2946
				'member_list' => $owner,
2947
				'not_deleted' => 0,
2948
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2949
			)
2950
		);
2951
		// ...And update the statistics accordingly - now including unread messages!.
2952
		while ($row = $smcFunc['db_fetch_assoc']($request))
2953
		{
2954
			if ($row['is_read'])
2955
				updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' . $row['num_deleted_messages']));
2956
			else
2957
				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']));
2958
2959
			// If this is the current member we need to make their message count correct.
2960
			if ($user_info['id'] == $row['id_member'])
2961
			{
2962
				$user_info['messages'] -= $row['num_deleted_messages'];
2963
				if (!($row['is_read']))
2964
					$user_info['unread_messages'] -= $row['num_deleted_messages'];
2965
			}
2966
		}
2967
		$smcFunc['db_free_result']($request);
2968
2969
		// Do the actual deletion.
2970
		$smcFunc['db_query']('', '
2971
			UPDATE {db_prefix}pm_recipients
2972
			SET deleted = {int:is_deleted}
2973
			WHERE id_member IN ({array_int:member_list})
2974
				AND deleted = {int:not_deleted}' . $where,
2975
			array(
2976
				'member_list' => $owner,
2977
				'is_deleted' => 1,
2978
				'not_deleted' => 0,
2979
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2980
			)
2981
		);
2982
2983
		$labels = array();
2984
2985
		// Get any labels that the owner may have applied to this PM
2986
		// The join is here to ensure we only get labels applied by the specified member(s)
2987
		$get_labels = $smcFunc['db_query']('', '
2988
			SELECT pml.id_label
2989
			FROM {db_prefix}pm_labels AS l
2990
				INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2991
			WHERE l.id_member IN ({array_int:member_list})' . $where,
2992
			array(
2993
				'member_list' => $owner,
2994
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2995
			)
2996
		);
2997
2998
		while ($row = $smcFunc['db_fetch_assoc']($get_labels))
2999
		{
3000
			$labels[] = $row['id_label'];
3001
		}
3002
3003
		$smcFunc['db_free_result']($get_labels);
3004
3005
		if (!empty($labels))
3006
		{
3007
			$smcFunc['db_query']('', '
3008
				DELETE FROM {db_prefix}pm_labeled_messages
3009
				WHERE id_label IN ({array_int:labels})' . $where,
3010
				array(
3011
					'labels' => $labels,
3012
					'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3013
				)
3014
			);
3015
		}
3016
	}
3017
3018
	// If sender and recipients all have deleted their message, it can be removed.
3019
	$request = $smcFunc['db_query']('', '
3020
		SELECT pm.id_pm AS sender, pmr.id_pm
3021
		FROM {db_prefix}personal_messages AS pm
3022
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.deleted = {int:not_deleted})
3023
		WHERE pm.deleted_by_sender = {int:is_deleted} AND pmr.id_pm is null
3024
			' . str_replace('id_pm', 'pm.id_pm', $where),
3025
		array(
3026
			'not_deleted' => 0,
3027
			'is_deleted' => 1,
3028
			'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3029
		)
3030
	);
3031
	$remove_pms = array();
3032
	while ($row = $smcFunc['db_fetch_assoc']($request))
3033
		$remove_pms[] = $row['sender'];
3034
	$smcFunc['db_free_result']($request);
3035
3036
	if (!empty($remove_pms))
3037
	{
3038
		$smcFunc['db_query']('', '
3039
			DELETE FROM {db_prefix}personal_messages
3040
			WHERE id_pm IN ({array_int:pm_list})',
3041
			array(
3042
				'pm_list' => $remove_pms,
3043
			)
3044
		);
3045
3046
		$smcFunc['db_query']('', '
3047
			DELETE FROM {db_prefix}pm_recipients
3048
			WHERE id_pm IN ({array_int:pm_list})',
3049
			array(
3050
				'pm_list' => $remove_pms,
3051
			)
3052
		);
3053
3054
		$smcFunc['db_query']('', '
3055
			DELETE FROM {db_prefix}pm_labeled_messages
3056
			WHERE id_pm IN ({array_int:pm_list})',
3057
			array(
3058
				'pm_list' => $remove_pms,
3059
			)
3060
		);
3061
	}
3062
3063
	// Any cached numbers may be wrong now.
3064
	cache_put_data('labelCounts:' . $user_info['id'], null, 720);
3065
}
3066
3067
/**
3068
 * Mark the specified personal messages read.
3069
 *
3070
 * @param array|null $personal_messages An array of PM IDs to mark or null to mark all
3071
 * @param int|null $label The ID of a label. If set, only messages with this label will be marked.
3072
 * @param int|null $owner If owner is set, marks messages owned by that member id
3073
 */
3074
function markMessages($personal_messages = null, $label = null, $owner = null)
3075
{
3076
	global $user_info, $context, $smcFunc;
3077
3078
	if ($owner === null)
3079
		$owner = $user_info['id'];
3080
3081
	$in_inbox = '';
3082
3083
	// Marking all messages with a specific label as read?
3084
	// If we know which PMs we're marking read, then we don't need label info
3085
	if ($personal_messages === null && $label !== null && $label != '-1')
3086
	{
3087
		$personal_messages = array();
3088
		$get_messages = $smcFunc['db_query']('', '
3089
			SELECT id_pm
3090
			FROM {db_prefix}pm_labeled_messages
3091
			WHERE id_label = {int:current_label}',
3092
			array(
3093
				'current_label' => $label,
3094
			)
3095
		);
3096
3097
		while ($row = $smcFunc['db_fetch_assoc']($get_messages))
3098
		{
3099
			$personal_messages[] = $row['id_pm'];
3100
		}
3101
3102
		$smcFunc['db_free_result']($get_messages);
3103
	}
3104
	elseif ($label = '-1')
0 ignored issues
show
Unused Code introduced by
The assignment to $label is dead and can be removed.
Loading history...
3105
	{
3106
		// Marking all PMs in your inbox read
3107
		$in_inbox = '
3108
			AND in_inbox = {int:in_inbox}';
3109
	}
3110
3111
	$smcFunc['db_query']('', '
3112
		UPDATE {db_prefix}pm_recipients
3113
		SET is_read = is_read | 1
3114
		WHERE id_member = {int:id_member}
3115
			AND NOT (is_read & 1 >= 1)' . ($personal_messages !== null ? '
3116
			AND id_pm IN ({array_int:personal_messages})' : '') . $in_inbox,
3117
		array(
3118
			'personal_messages' => $personal_messages,
3119
			'id_member' => $owner,
3120
			'in_inbox' => 1,
3121
		)
3122
	);
3123
3124
	// If something wasn't marked as read, get the number of unread messages remaining.
3125
	if ($smcFunc['db_affected_rows']() > 0)
3126
	{
3127
		if ($owner == $user_info['id'])
3128
		{
3129
			foreach ($context['labels'] as $label)
3130
				$context['labels'][(int) $label['id']]['unread_messages'] = 0;
3131
		}
3132
3133
		$result = $smcFunc['db_query']('', '
3134
			SELECT id_pm, in_inbox, COUNT(*) AS num
3135
			FROM {db_prefix}pm_recipients
3136
			WHERE id_member = {int:id_member}
3137
				AND NOT (is_read & 1 >= 1)
3138
				AND deleted = {int:is_not_deleted}
3139
			GROUP BY id_pm, in_inbox',
3140
			array(
3141
				'id_member' => $owner,
3142
				'is_not_deleted' => 0,
3143
			)
3144
		);
3145
		$total_unread = 0;
3146
		while ($row = $smcFunc['db_fetch_assoc']($result))
3147
		{
3148
			$total_unread += $row['num'];
3149
3150
			if ($owner != $user_info['id'] || empty($row['id_pm']))
3151
				continue;
3152
3153
			$this_labels = array();
3154
3155
			// Get all the labels
3156
			$result2 = $smcFunc['db_query']('', '
3157
				SELECT pml.id_label
3158
				FROM {db_prefix}pm_labels AS l
3159
					INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
3160
				WHERE l.id_member = {int:id_member}
3161
					AND pml.id_pm = {int:current_pm}',
3162
				array(
3163
					'id_member' => $owner,
3164
					'current_pm' => $row['id_pm'],
3165
				)
3166
			);
3167
3168
			while ($row2 = $smcFunc['db_fetch_assoc']($result2))
3169
			{
3170
				$this_labels[] = $row2['id_label'];
3171
			}
3172
3173
			$smcFunc['db_free_result']($result2);
3174
3175
			foreach ($this_labels as $this_label)
3176
				$context['labels'][$this_label]['unread_messages'] += $row['num'];
3177
3178
			if ($row['in_inbox'] == 1)
3179
				$context['labels'][-1]['unread_messages'] += $row['num'];
3180
		}
3181
		$smcFunc['db_free_result']($result);
3182
3183
		// Need to store all this.
3184
		cache_put_data('labelCounts:' . $owner, $context['labels'], 720);
3185
		updateMemberData($owner, array('unread_messages' => $total_unread));
3186
3187
		// If it was for the current member, reflect this in the $user_info array too.
3188
		if ($owner == $user_info['id'])
3189
			$user_info['unread_messages'] = $total_unread;
3190
	}
3191
}
3192
3193
/**
3194
 * This function handles adding, deleting and editing labels on messages.
3195
 */
3196
function ManageLabels()
3197
{
3198
	global $txt, $context, $user_info, $scripturl, $smcFunc;
3199
3200
	// Build the link tree elements...
3201
	$context['linktree'][] = array(
3202
		'url' => $scripturl . '?action=pm;sa=manlabels',
3203
		'name' => $txt['pm_manage_labels']
3204
	);
3205
3206
	$context['page_title'] = $txt['pm_manage_labels'];
3207
	$context['sub_template'] = 'labels';
3208
3209
	$the_labels = array();
3210
	$labels_to_add = array();
3211
	$labels_to_remove = array();
3212
	$label_updates = array();
3213
3214
	// Add all existing labels to the array to save, slashing them as necessary...
3215
	foreach ($context['labels'] as $label)
3216
	{
3217
		if ($label['id'] != -1)
3218
			$the_labels[$label['id']] = $label['name'];
3219
	}
3220
3221
	if (isset($_POST[$context['session_var']]))
3222
	{
3223
		checkSession();
3224
3225
		// This will be for updating messages.
3226
		$message_changes = array();
3227
		$rule_changes = array();
3228
3229
		// Will most likely need this.
3230
		LoadRules();
3231
3232
		// Adding a new label?
3233
		if (isset($_POST['add']))
3234
		{
3235
			$_POST['label'] = strtr($smcFunc['htmlspecialchars'](trim($_POST['label'])), array(',' => '&#044;'));
3236
3237
			if ($smcFunc['strlen']($_POST['label']) > 30)
3238
				$_POST['label'] = $smcFunc['substr']($_POST['label'], 0, 30);
3239
			if ($_POST['label'] != '')
3240
			{
3241
				$the_labels[] = $_POST['label'];
3242
				$labels_to_add[] = $_POST['label'];
3243
			}
3244
		}
3245
		// Deleting an existing label?
3246
		elseif (isset($_POST['delete'], $_POST['delete_label']))
3247
		{
3248
			foreach ($_POST['delete_label'] AS $label => $dummy)
3249
			{
3250
				unset($the_labels[$label]);
3251
				$labels_to_remove[] = $label;
3252
			}
3253
		}
3254
		// The hardest one to deal with... changes.
3255
		elseif (isset($_POST['save']) && !empty($_POST['label_name']))
3256
		{
3257
			foreach ($the_labels as $id => $name)
3258
			{
3259
				if ($id == -1)
3260
					continue;
3261
				elseif (isset($_POST['label_name'][$id]))
3262
				{
3263
					$_POST['label_name'][$id] = trim(strtr($smcFunc['htmlspecialchars']($_POST['label_name'][$id]), array(',' => '&#044;')));
3264
3265
					if ($smcFunc['strlen']($_POST['label_name'][$id]) > 30)
3266
						$_POST['label_name'][$id] = $smcFunc['substr']($_POST['label_name'][$id], 0, 30);
3267
					if ($_POST['label_name'][$id] != '')
3268
					{
3269
						// Changing the name of this label?
3270
						if ($the_labels[$id] != $_POST['label_name'][$id])
3271
							$label_updates[$id] = $_POST['label_name'][$id];
3272
3273
						$the_labels[(int) $id] = $_POST['label_name'][$id];
3274
					}
3275
					else
3276
					{
3277
						unset($the_labels[(int) $id]);
3278
						$labels_to_remove[] = $id;
3279
						$message_changes[(int) $id] = true;
3280
					}
3281
				}
3282
			}
3283
		}
3284
3285
		// Save any new labels
3286
		if (!empty($labels_to_add))
3287
		{
3288
			$inserts = array();
3289
			foreach ($labels_to_add AS $label)
3290
				$inserts[] = array($user_info['id'], $label);
3291
3292
			$smcFunc['db_insert']('', '{db_prefix}pm_labels', array('id_member' => 'int', 'name' => 'string-30'), $inserts, array());
3293
		}
3294
3295
		// Update existing labels as needed
3296
		if (!empty($label_upates))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $label_upates does not exist. Did you maybe mean $label_updates?
Loading history...
3297
		{
3298
			foreach ($label_updates AS $id => $name)
3299
			{
3300
				$smcFunc['db_query']('', '
3301
					UPDATE {db_prefix}labels
3302
					SET name = {string:name}
3303
					WHERE id_label = {int:id_label}',
3304
					array(
3305
						'name' => $name,
3306
						'id' => $id
3307
					)
3308
				);
3309
			}
3310
		}
3311
3312
		// Now the fun part... Deleting labels.
3313
		if (!empty($labels_to_remove))
3314
		{
3315
			// First delete the labels
3316
			$smcFunc['db_query']('', '
3317
				DELETE FROM {db_prefix}pm_labels
3318
				WHERE id_label IN ({array_int:labels_to_delete})',
3319
				array(
3320
					'labels_to_delete' => $labels_to_remove,
3321
				)
3322
			);
3323
3324
			// Now remove the now-deleted labels from any PMs...
3325
			$smcFunc['db_query']('', '
3326
				DELETE FROM {db_prefix}pm_labeled_messages
3327
				WHERE id_label IN ({array_int:labels_to_delete})',
3328
				array(
3329
					'labels_to_delete' => $labels_to_remove,
3330
				)
3331
			);
3332
3333
			// Get any PMs with no labels which aren't in the inbox
3334
			$get_stranded_pms = $smcFunc['db_query']('', '
3335
				SELECT pmr.id_pm
3336
				FROM {db_prefix}pm_recipients AS pmr
3337
					LEFT JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)
3338
				WHERE pml.id_label IS NULL
3339
					AND pmr.in_inbox = {int:not_in_inbox}
3340
					AND pmr.deleted = {int:not_deleted}
3341
					AND pmr.id_member = {int:current_member}',
3342
				array(
3343
					'not_in_inbox' => 0,
3344
					'not_deleted' => 0,
3345
					'current_member' => $user_info['id'],
3346
				)
3347
			);
3348
3349
			$stranded_messages = array();
3350
			while ($row = $smcFunc['db_fetch_assoc']($get_stranded_pms))
3351
			{
3352
				$stranded_messages[] = $row['id_pm'];
3353
			}
3354
3355
			$smcFunc['db_free_result']($get_stranded_pms);
3356
3357
			// Move these back to the inbox if necessary
3358
			if (!empty($stranded_messages))
3359
			{
3360
				// We now have more messages in the inbox
3361
				$context['labels'][-1]['messages'] += count($stranded_messages);
3362
				$smcFunc['db_query']('', '
3363
					UPDATE {db_prefix}pm_recipients
3364
					SET in_inbox = {int:in_inbox}
3365
					WHERE id_pm IN ({array_int:stranded_messages})
3366
						AND id_member = {int:current_member}',
3367
					array(
3368
						'stranded_messages' => $stranded_messages,
3369
						'in_inbox' => 1,
3370
					)
3371
				);
3372
			}
3373
3374
			// Now do the same the rules - check through each rule.
3375
			foreach ($context['rules'] as $k => $rule)
3376
			{
3377
				// Each action...
3378
				foreach ($rule['actions'] as $k2 => $action)
3379
				{
3380
					if ($action['t'] != 'lab' || !in_array($action['v'], $labels_to_remove))
3381
						continue;
3382
3383
					$rule_changes[] = $rule['id'];
3384
3385
					// Can't apply this label anymore if it doesn't exist
3386
					unset($context['rules'][$k]['actions'][$k2]);
3387
				}
3388
			}
3389
		}
3390
3391
		// If we have rules to change do so now.
3392
		if (!empty($rule_changes))
3393
		{
3394
			$rule_changes = array_unique($rule_changes);
3395
			// Update/delete as appropriate.
3396
			foreach ($rule_changes as $k => $id)
3397
				if (!empty($context['rules'][$id]['actions']))
3398
				{
3399
					$smcFunc['db_query']('', '
3400
						UPDATE {db_prefix}pm_rules
3401
						SET actions = {string:actions}
3402
						WHERE id_rule = {int:id_rule}
3403
							AND id_member = {int:current_member}',
3404
						array(
3405
							'current_member' => $user_info['id'],
3406
							'id_rule' => $id,
3407
							'actions' => $smcFunc['json_encode']($context['rules'][$id]['actions']),
3408
						)
3409
					);
3410
					unset($rule_changes[$k]);
3411
				}
3412
3413
			// Anything left here means it's lost all actions...
3414
			if (!empty($rule_changes))
3415
				$smcFunc['db_query']('', '
3416
					DELETE FROM {db_prefix}pm_rules
3417
					WHERE id_rule IN ({array_int:rule_list})
3418
						AND id_member = {int:current_member}',
3419
					array(
3420
						'current_member' => $user_info['id'],
3421
						'rule_list' => $rule_changes,
3422
					)
3423
				);
3424
		}
3425
3426
		// Make sure we're not caching this!
3427
		cache_put_data('labelCounts:' . $user_info['id'], null, 720);
3428
3429
		// To make the changes appear right away, redirect.
3430
		redirectexit('action=pm;sa=manlabels');
3431
	}
3432
}
3433
3434
/**
3435
 * Allows to edit Personal Message Settings.
3436
 *
3437
 * @uses Profile.php
3438
 * @uses Profile-Modify.php
3439
 * @uses Profile template.
3440
 * @uses Profile language file.
3441
 */
3442
function MessageSettings()
3443
{
3444
	global $txt, $user_info, $context, $sourcedir;
3445
	global $scripturl, $profile_vars, $cur_profile, $user_profile;
3446
3447
	// Need this for the display.
3448
	require_once($sourcedir . '/Profile.php');
3449
	require_once($sourcedir . '/Profile-Modify.php');
3450
3451
	// We want them to submit back to here.
3452
	$context['profile_custom_submit_url'] = $scripturl . '?action=pm;sa=settings;save';
3453
3454
	loadMemberData($user_info['id'], false, 'profile');
3455
	$cur_profile = $user_profile[$user_info['id']];
3456
3457
	loadLanguage('Profile');
3458
	loadTemplate('Profile');
3459
3460
	// Since this is internally handled with the profile code because that's how it was done ages ago
3461
	// we have to set everything up for handling this...
3462
	$context['page_title'] = $txt['pm_settings'];
3463
	$context['user']['is_owner'] = true;
3464
	$context['id_member'] = $user_info['id'];
3465
	$context['require_password'] = false;
3466
	$context['menu_item_selected'] = 'settings';
3467
	$context['submit_button_text'] = $txt['pm_settings'];
3468
	$context['profile_header_text'] = $txt['personal_messages'];
3469
	$context['sub_template'] = 'edit_options';
3470
	$context['page_desc'] = $txt['pm_settings_desc'];
3471
3472
	loadThemeOptions($user_info['id']);
3473
	loadCustomFields($user_info['id'], 'pmprefs');
3474
3475
	// Add our position to the linktree.
3476
	$context['linktree'][] = array(
3477
		'url' => $scripturl . '?action=pm;sa=settings',
3478
		'name' => $txt['pm_settings']
3479
	);
3480
3481
	// Are they saving?
3482
	if (isset($_REQUEST['save']))
3483
	{
3484
		checkSession();
3485
3486
		// Mimic what profile would do.
3487
		$_POST = htmltrim__recursive($_POST);
3488
		$_POST = htmlspecialchars__recursive($_POST);
3489
3490
		// Save the fields.
3491
		saveProfileFields();
3492
3493
		if (!empty($profile_vars))
3494
			updateMemberData($user_info['id'], $profile_vars);
3495
	}
3496
3497
	setupProfileContext(
3498
		array(
3499
			'pm_prefs',
3500
		)
3501
	);
3502
}
3503
3504
/**
3505
 * Allows the user to report a personal message to an administrator.
3506
 *
3507
 * - In the first instance requires that the ID of the message to report is passed through $_GET.
3508
 * - It allows the user to report to either a particular administrator - or the whole admin team.
3509
 * - It will forward on a copy of the original message without allowing the reporter to make changes.
3510
 *
3511
 * @uses report_message sub-template.
3512
 */
3513
function ReportMessage()
3514
{
3515
	global $txt, $context, $scripturl;
3516
	global $user_info, $language, $modSettings, $smcFunc;
3517
3518
	// Check that this feature is even enabled!
3519
	if (empty($modSettings['enableReportPM']) || empty($_REQUEST['pmsg']))
3520
		fatal_lang_error('no_access', false);
3521
3522
	$pmsg = (int) $_REQUEST['pmsg'];
3523
3524
	if (!isAccessiblePM($pmsg, 'inbox'))
3525
		fatal_lang_error('no_access', false);
3526
3527
	$context['pm_id'] = $pmsg;
3528
	$context['page_title'] = $txt['pm_report_title'];
3529
3530
	// If we're here, just send the user to the template, with a few useful context bits.
3531
	if (!isset($_POST['report']))
3532
	{
3533
		$context['sub_template'] = 'report_message';
3534
3535
		// @todo I don't like being able to pick who to send it to.  Favoritism, etc. sucks.
3536
		// Now, get all the administrators.
3537
		$request = $smcFunc['db_query']('', '
3538
			SELECT id_member, real_name
3539
			FROM {db_prefix}members
3540
			WHERE id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0
3541
			ORDER BY real_name',
3542
			array(
3543
				'admin_group' => 1,
3544
			)
3545
		);
3546
		$context['admins'] = array();
3547
		while ($row = $smcFunc['db_fetch_assoc']($request))
3548
			$context['admins'][$row['id_member']] = $row['real_name'];
3549
		$smcFunc['db_free_result']($request);
3550
3551
		// How many admins in total?
3552
		$context['admin_count'] = count($context['admins']);
3553
	}
3554
	// Otherwise, let's get down to the sending stuff.
3555
	else
3556
	{
3557
		// Check the session before proceeding any further!
3558
		checkSession();
3559
3560
		// First, pull out the message contents, and verify it actually went to them!
3561
		$request = $smcFunc['db_query']('', '
3562
			SELECT pm.subject, pm.body, pm.msgtime, pm.id_member_from, COALESCE(m.real_name, pm.from_name) AS sender_name
3563
			FROM {db_prefix}personal_messages AS pm
3564
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
3565
				LEFT JOIN {db_prefix}members AS m ON (m.id_member = pm.id_member_from)
3566
			WHERE pm.id_pm = {int:id_pm}
3567
				AND pmr.id_member = {int:current_member}
3568
				AND pmr.deleted = {int:not_deleted}
3569
			LIMIT 1',
3570
			array(
3571
				'current_member' => $user_info['id'],
3572
				'id_pm' => $context['pm_id'],
3573
				'not_deleted' => 0,
3574
			)
3575
		);
3576
		// Can only be a hacker here!
3577
		if ($smcFunc['db_num_rows']($request) == 0)
3578
			fatal_lang_error('no_access', false);
3579
		list ($subject, $body, $time, $memberFromID, $memberFromName) = $smcFunc['db_fetch_row']($request);
3580
		$smcFunc['db_free_result']($request);
3581
3582
		// Remove the line breaks...
3583
		$body = preg_replace('~<br ?/?' . '>~i', "\n", $body);
3584
3585
		// Get any other recipients of the email.
3586
		$request = $smcFunc['db_query']('', '
3587
			SELECT mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, pmr.bcc
3588
			FROM {db_prefix}pm_recipients AS pmr
3589
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
3590
			WHERE pmr.id_pm = {int:id_pm}
3591
				AND pmr.id_member != {int:current_member}',
3592
			array(
3593
				'current_member' => $user_info['id'],
3594
				'id_pm' => $context['pm_id'],
3595
			)
3596
		);
3597
		$recipients = array();
3598
		$hidden_recipients = 0;
3599
		while ($row = $smcFunc['db_fetch_assoc']($request))
3600
		{
3601
			// If it's hidden still don't reveal their names - privacy after all ;)
3602
			if ($row['bcc'])
3603
				$hidden_recipients++;
3604
			else
3605
				$recipients[] = '[url=' . $scripturl . '?action=profile;u=' . $row['id_member_to'] . ']' . $row['to_name'] . '[/url]';
3606
		}
3607
		$smcFunc['db_free_result']($request);
3608
3609
		if ($hidden_recipients)
3610
			$recipients[] = sprintf($txt['pm_report_pm_hidden'], $hidden_recipients);
3611
3612
		// Now let's get out and loop through the admins.
3613
		$request = $smcFunc['db_query']('', '
3614
			SELECT id_member, real_name, lngfile
3615
			FROM {db_prefix}members
3616
			WHERE (id_group = {int:admin_id} OR FIND_IN_SET({int:admin_id}, additional_groups) != 0)
3617
				' . (empty($_POST['id_admin']) ? '' : 'AND id_member = {int:specific_admin}') . '
3618
			ORDER BY lngfile',
3619
			array(
3620
				'admin_id' => 1,
3621
				'specific_admin' => isset($_POST['id_admin']) ? (int) $_POST['id_admin'] : 0,
3622
			)
3623
		);
3624
3625
		// Maybe we shouldn't advertise this?
3626
		if ($smcFunc['db_num_rows']($request) == 0)
3627
			fatal_lang_error('no_access', false);
3628
3629
		$memberFromName = un_htmlspecialchars($memberFromName);
3630
3631
		// Prepare the message storage array.
3632
		$messagesToSend = array();
3633
		// Loop through each admin, and add them to the right language pile...
3634
		while ($row = $smcFunc['db_fetch_assoc']($request))
3635
		{
3636
			// Need to send in the correct language!
3637
			$cur_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'];
3638
3639
			if (!isset($messagesToSend[$cur_language]))
3640
			{
3641
				loadLanguage('PersonalMessage', $cur_language, false);
3642
3643
				// Make the body.
3644
				$report_body = str_replace(array('{REPORTER}', '{SENDER}'), array(un_htmlspecialchars($user_info['name']), $memberFromName), $txt['pm_report_pm_user_sent']);
3645
				$report_body .= "\n" . '[b]' . $_POST['reason'] . '[/b]' . "\n\n";
3646
				if (!empty($recipients))
3647
					$report_body .= $txt['pm_report_pm_other_recipients'] . ' ' . implode(', ', $recipients) . "\n\n";
3648
				$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]';
3649
3650
				// Plonk it in the array ;)
3651
				$messagesToSend[$cur_language] = array(
3652
					'subject' => ($smcFunc['strpos']($subject, $txt['pm_report_pm_subject']) === false ? $txt['pm_report_pm_subject'] : '') . un_htmlspecialchars($subject),
3653
					'body' => $report_body,
3654
					'recipients' => array(
3655
						'to' => array(),
3656
						'bcc' => array()
3657
					),
3658
				);
3659
			}
3660
3661
			// Add them to the list.
3662
			$messagesToSend[$cur_language]['recipients']['to'][$row['id_member']] = $row['id_member'];
3663
		}
3664
		$smcFunc['db_free_result']($request);
3665
3666
		// Send a different email for each language.
3667
		foreach ($messagesToSend as $lang => $message)
3668
			sendpm($message['recipients'], $message['subject'], $message['body']);
3669
3670
		// Give the user their own language back!
3671
		if (!empty($modSettings['userLanguage']))
3672
			loadLanguage('PersonalMessage', '', false);
3673
3674
		// Leave them with a template.
3675
		$context['sub_template'] = 'report_message_complete';
3676
	}
3677
}
3678
3679
/**
3680
 * List all rules, and allow adding/entering etc...
3681
 */
3682
function ManageRules()
3683
{
3684
	global $txt, $context, $user_info, $scripturl, $smcFunc;
3685
3686
	// The link tree - gotta have this :o
3687
	$context['linktree'][] = array(
3688
		'url' => $scripturl . '?action=pm;sa=manrules',
3689
		'name' => $txt['pm_manage_rules']
3690
	);
3691
3692
	$context['page_title'] = $txt['pm_manage_rules'];
3693
	$context['sub_template'] = 'rules';
3694
3695
	// Load them... load them!!
3696
	LoadRules();
3697
3698
	// Likely to need all the groups!
3699
	$request = $smcFunc['db_query']('', '
3700
		SELECT mg.id_group, mg.group_name, COALESCE(gm.id_member, 0) AS can_moderate, mg.hidden
3701
		FROM {db_prefix}membergroups AS mg
3702
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
3703
		WHERE mg.min_posts = {int:min_posts}
3704
			AND mg.id_group != {int:moderator_group}
3705
			AND mg.hidden = {int:not_hidden}
3706
		ORDER BY mg.group_name',
3707
		array(
3708
			'current_member' => $user_info['id'],
3709
			'min_posts' => -1,
3710
			'moderator_group' => 3,
3711
			'not_hidden' => 0,
3712
		)
3713
	);
3714
	$context['groups'] = array();
3715
	while ($row = $smcFunc['db_fetch_assoc']($request))
3716
	{
3717
		// Hide hidden groups!
3718
		if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups'))
3719
			continue;
3720
3721
		$context['groups'][$row['id_group']] = $row['group_name'];
3722
	}
3723
	$smcFunc['db_free_result']($request);
3724
3725
	// Applying all rules?
3726
	if (isset($_GET['apply']))
3727
	{
3728
		checkSession('get');
3729
3730
		ApplyRules(true);
3731
		redirectexit('action=pm;sa=manrules');
3732
	}
3733
	// Editing a specific one?
3734
	if (isset($_GET['add']))
3735
	{
3736
		$context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']]) ? (int) $_GET['rid'] : 0;
3737
		$context['sub_template'] = 'add_rule';
3738
3739
		// Current rule information...
3740
		if ($context['rid'])
3741
		{
3742
			$context['rule'] = $context['rules'][$context['rid']];
3743
			$members = array();
3744
			// Need to get member names!
3745
			foreach ($context['rule']['criteria'] as $k => $criteria)
3746
				if ($criteria['t'] == 'mid' && !empty($criteria['v']))
3747
					$members[(int) $criteria['v']] = $k;
3748
3749
			if (!empty($members))
3750
			{
3751
				$request = $smcFunc['db_query']('', '
3752
					SELECT id_member, member_name
3753
					FROM {db_prefix}members
3754
					WHERE id_member IN ({array_int:member_list})',
3755
					array(
3756
						'member_list' => array_keys($members),
3757
					)
3758
				);
3759
				while ($row = $smcFunc['db_fetch_assoc']($request))
3760
					$context['rule']['criteria'][$members[$row['id_member']]]['v'] = $row['member_name'];
3761
				$smcFunc['db_free_result']($request);
3762
			}
3763
		}
3764
		else
3765
			$context['rule'] = array(
3766
				'id' => '',
3767
				'name' => '',
3768
				'criteria' => array(),
3769
				'actions' => array(),
3770
				'logic' => 'and',
3771
			);
3772
	}
3773
	// Saving?
3774
	elseif (isset($_GET['save']))
3775
	{
3776
		checkSession();
3777
		$context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']]) ? (int) $_GET['rid'] : 0;
3778
3779
		// Name is easy!
3780
		$ruleName = $smcFunc['htmlspecialchars'](trim($_POST['rule_name']));
3781
		if (empty($ruleName))
3782
			fatal_lang_error('pm_rule_no_name', false);
3783
3784
		// Sanity check...
3785
		if (empty($_POST['ruletype']) || empty($_POST['acttype']))
3786
			fatal_lang_error('pm_rule_no_criteria', false);
3787
3788
		// Let's do the criteria first - it's also hardest!
3789
		$criteria = array();
3790
		foreach ($_POST['ruletype'] as $ind => $type)
3791
		{
3792
			// Check everything is here...
3793
			if ($type == 'gid' && (!isset($_POST['ruledefgroup'][$ind]) || !isset($context['groups'][$_POST['ruledefgroup'][$ind]])))
3794
				continue;
3795
			elseif ($type != 'bud' && !isset($_POST['ruledef'][$ind]))
3796
				continue;
3797
3798
			// Members need to be found.
3799
			if ($type == 'mid')
3800
			{
3801
				$name = trim($_POST['ruledef'][$ind]);
3802
				$request = $smcFunc['db_query']('', '
3803
					SELECT id_member
3804
					FROM {db_prefix}members
3805
					WHERE real_name = {string:member_name}
3806
						OR member_name = {string:member_name}',
3807
					array(
3808
						'member_name' => $name,
3809
					)
3810
				);
3811
				if ($smcFunc['db_num_rows']($request) == 0)
3812
				{
3813
					loadLanguage('Errors');
3814
					fatal_lang_error('invalid_username', false);
3815
				}
3816
				list ($memID) = $smcFunc['db_fetch_row']($request);
3817
				$smcFunc['db_free_result']($request);
3818
3819
				$criteria[] = array('t' => 'mid', 'v' => $memID);
3820
			}
3821
			elseif ($type == 'bud')
3822
				$criteria[] = array('t' => 'bud', 'v' => 1);
3823
			elseif ($type == 'gid')
3824
				$criteria[] = array('t' => 'gid', 'v' => (int) $_POST['ruledefgroup'][$ind]);
3825
			elseif (in_array($type, array('sub', 'msg')) && trim($_POST['ruledef'][$ind]) != '')
3826
				$criteria[] = array('t' => $type, 'v' => $smcFunc['htmlspecialchars'](trim($_POST['ruledef'][$ind])));
3827
		}
3828
3829
		// Also do the actions!
3830
		$actions = array();
3831
		$doDelete = 0;
3832
		$isOr = $_POST['rule_logic'] == 'or' ? 1 : 0;
3833
		foreach ($_POST['acttype'] as $ind => $type)
3834
		{
3835
			// Picking a valid label?
3836
			if ($type == 'lab' && (!ctype_digit((string) $ind) || !isset($_POST['labdef'][$ind]) || !isset($context['labels'][$_POST['labdef'][$ind]])))
3837
				continue;
3838
3839
			// Record what we're doing.
3840
			if ($type == 'del')
3841
				$doDelete = 1;
3842
			elseif ($type == 'lab')
3843
				$actions[] = array('t' => 'lab', 'v' => (int) $_POST['labdef'][$ind]);
3844
		}
3845
3846
		if (empty($criteria) || (empty($actions) && !$doDelete))
3847
			fatal_lang_error('pm_rule_no_criteria', false);
3848
3849
		// What are we storing?
3850
		$criteria = $smcFunc['json_encode']($criteria);
3851
		$actions = $smcFunc['json_encode']($actions);
3852
3853
		// Create the rule?
3854
		if (empty($context['rid']))
3855
			$smcFunc['db_insert']('',
3856
				'{db_prefix}pm_rules',
3857
				array(
3858
					'id_member' => 'int', 'rule_name' => 'string', 'criteria' => 'string', 'actions' => 'string',
3859
					'delete_pm' => 'int', 'is_or' => 'int',
3860
				),
3861
				array(
3862
					$user_info['id'], $ruleName, $criteria, $actions, $doDelete, $isOr,
3863
				),
3864
				array('id_rule')
3865
			);
3866
		else
3867
			$smcFunc['db_query']('', '
3868
				UPDATE {db_prefix}pm_rules
3869
				SET rule_name = {string:rule_name}, criteria = {string:criteria}, actions = {string:actions},
3870
					delete_pm = {int:delete_pm}, is_or = {int:is_or}
3871
				WHERE id_rule = {int:id_rule}
3872
					AND id_member = {int:current_member}',
3873
				array(
3874
					'current_member' => $user_info['id'],
3875
					'delete_pm' => $doDelete,
3876
					'is_or' => $isOr,
3877
					'id_rule' => $context['rid'],
3878
					'rule_name' => $ruleName,
3879
					'criteria' => $criteria,
3880
					'actions' => $actions,
3881
				)
3882
			);
3883
3884
		redirectexit('action=pm;sa=manrules');
3885
	}
3886
	// Deleting?
3887
	elseif (isset($_POST['delselected']) && !empty($_POST['delrule']))
3888
	{
3889
		checkSession();
3890
		$toDelete = array();
3891
		foreach ($_POST['delrule'] as $k => $v)
3892
			$toDelete[] = (int) $k;
3893
3894
		if (!empty($toDelete))
3895
			$smcFunc['db_query']('', '
3896
				DELETE FROM {db_prefix}pm_rules
3897
				WHERE id_rule IN ({array_int:delete_list})
3898
					AND id_member = {int:current_member}',
3899
				array(
3900
					'current_member' => $user_info['id'],
3901
					'delete_list' => $toDelete,
3902
				)
3903
			);
3904
3905
		redirectexit('action=pm;sa=manrules');
3906
	}
3907
}
3908
3909
/**
3910
 * This will apply rules to all unread messages. If all_messages is set will, clearly, do it to all!
3911
 *
3912
 * @param bool $all_messages Whether to apply this to all messages or just unread ones
3913
 */
3914
function ApplyRules($all_messages = false)
3915
{
3916
	global $user_info, $smcFunc, $context, $options;
3917
3918
	// Want this - duh!
3919
	loadRules();
3920
3921
	// No rules?
3922
	if (empty($context['rules']))
3923
		return;
3924
3925
	// Just unread ones?
3926
	$ruleQuery = $all_messages ? '' : ' AND pmr.is_new = 1';
3927
3928
	// @todo Apply all should have timeout protection!
3929
	// Get all the messages that match this.
3930
	$request = $smcFunc['db_query']('', '
3931
		SELECT
3932
			pmr.id_pm, pm.id_member_from, pm.subject, pm.body, mem.id_group
3933
		FROM {db_prefix}pm_recipients AS pmr
3934
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
3935
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
3936
		WHERE pmr.id_member = {int:current_member}
3937
			AND pmr.deleted = {int:not_deleted}
3938
			' . $ruleQuery,
3939
		array(
3940
			'current_member' => $user_info['id'],
3941
			'not_deleted' => 0,
3942
		)
3943
	);
3944
	$actions = array();
3945
	while ($row = $smcFunc['db_fetch_assoc']($request))
3946
	{
3947
		foreach ($context['rules'] as $rule)
3948
		{
3949
			$match = false;
3950
			// Loop through all the criteria hoping to make a match.
3951
			foreach ($rule['criteria'] as $criterium)
3952
			{
3953
				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))
3954
					$match = true;
3955
				// If we're adding and one criteria don't match then we stop!
3956
				elseif ($rule['logic'] == 'and')
3957
				{
3958
					$match = false;
3959
					break;
3960
				}
3961
			}
3962
3963
			// If we have a match the rule must be true - act!
3964
			if ($match)
3965
			{
3966
				if ($rule['delete'])
3967
					$actions['deletes'][] = $row['id_pm'];
3968
				else
3969
				{
3970
					foreach ($rule['actions'] as $ruleAction)
3971
					{
3972
						if ($ruleAction['t'] == 'lab')
3973
						{
3974
							// Get a basic pot started!
3975
							if (!isset($actions['labels'][$row['id_pm']]))
3976
								$actions['labels'][$row['id_pm']] = array();
3977
3978
							$actions['labels'][$row['id_pm']][] = $ruleAction['v'];
3979
						}
3980
					}
3981
				}
3982
			}
3983
		}
3984
	}
3985
	$smcFunc['db_free_result']($request);
3986
3987
	// Deletes are easy!
3988
	if (!empty($actions['deletes']))
3989
		deleteMessages($actions['deletes']);
3990
3991
	// Relabel?
3992
	if (!empty($actions['labels']))
3993
	{
3994
		foreach ($actions['labels'] as $pm => $labels)
3995
		{
3996
			// Quickly check each label is valid!
3997
			$realLabels = array();
3998
			foreach ($context['labels'] as $label)
3999
			{
4000
				if (in_array($label['id'], $labels) && $label['id'] != -1 || empty($options['pm_remove_inbox_label']))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (in_array($label['id'], ...m_remove_inbox_label']), Probably Intended Meaning: in_array($label['id'], $..._remove_inbox_label']))
Loading history...
4001
				{
4002
					// Make sure this stays in the inbox
4003
					if ($label['id'] == '-1')
4004
					{
4005
						$smcFunc['db_query']('', '
4006
							UPDATE {db_prefix}pm_recipients
4007
							SET in_inbox = {int:in_inbox}
4008
							WHERE id_pm = {int:id_pm}
4009
								AND id_member = {int:current_member}',
4010
							array(
4011
								'in_inbox' => 1,
4012
								'id_pm' => $pm,
4013
								'current_member' => $user_info['id'],
4014
							)
4015
						);
4016
					}
4017
					else
4018
					{
4019
						$realLabels[] = $label['id'];
4020
					}
4021
				}
4022
			}
4023
4024
			$inserts = array();
4025
			// Now we insert the label info
4026
			foreach ($realLabels as $a_label)
4027
				$inserts[] = array($pm, $a_label);
4028
4029
			$smcFunc['db_insert']('ignore',
4030
				'{db_prefix}pm_labeled_messages',
4031
				array('id_pm' => 'int', 'id_label' => 'int'),
4032
				$inserts,
4033
				array()
4034
			);
4035
		}
4036
	}
4037
}
4038
4039
/**
4040
 * Load up all the rules for the current user.
4041
 *
4042
 * @param bool $reload Whether or not to reload all the rules from the database if $context['rules'] is set
4043
 */
4044
function LoadRules($reload = false)
4045
{
4046
	global $user_info, $context, $smcFunc;
4047
4048
	if (isset($context['rules']) && !$reload)
4049
		return;
4050
4051
	$request = $smcFunc['db_query']('', '
4052
		SELECT
4053
			id_rule, rule_name, criteria, actions, delete_pm, is_or
4054
		FROM {db_prefix}pm_rules
4055
		WHERE id_member = {int:current_member}',
4056
		array(
4057
			'current_member' => $user_info['id'],
4058
		)
4059
	);
4060
	$context['rules'] = array();
4061
	// Simply fill in the data!
4062
	while ($row = $smcFunc['db_fetch_assoc']($request))
4063
	{
4064
		$context['rules'][$row['id_rule']] = array(
4065
			'id' => $row['id_rule'],
4066
			'name' => $row['rule_name'],
4067
			'criteria' => $smcFunc['json_decode']($row['criteria'], true),
4068
			'actions' => $smcFunc['json_decode']($row['actions'], true),
4069
			'delete' => $row['delete_pm'],
4070
			'logic' => $row['is_or'] ? 'or' : 'and',
4071
		);
4072
4073
		if ($row['delete_pm'])
4074
			$context['rules'][$row['id_rule']]['actions'][] = array('t' => 'del', 'v' => 1);
4075
	}
4076
	$smcFunc['db_free_result']($request);
4077
}
4078
4079
/**
4080
 * Check if the PM is available to the current user.
4081
 *
4082
 * @param int $pmID The ID of the PM
4083
 * @param string $validFor Which folders this is valud for - can be 'inbox', 'outbox' or 'in_or_outbox'
4084
 * @return boolean Whether the PM is accessible in that folder for the current user
4085
 */
4086
function isAccessiblePM($pmID, $validFor = 'in_or_outbox')
4087
{
4088
	global $user_info, $smcFunc;
4089
4090
	$request = $smcFunc['db_query']('', '
4091
		SELECT
4092
			pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted} AS valid_for_outbox,
4093
			pmr.id_pm IS NOT NULL AS valid_for_inbox
4094
		FROM {db_prefix}personal_messages AS pm
4095
			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})
4096
		WHERE pm.id_pm = {int:id_pm}
4097
			AND ((pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted}) OR pmr.id_pm IS NOT NULL)',
4098
		array(
4099
			'id_pm' => $pmID,
4100
			'id_current_member' => $user_info['id'],
4101
			'not_deleted' => 0,
4102
		)
4103
	);
4104
4105
	if ($smcFunc['db_num_rows']($request) === 0)
4106
	{
4107
		$smcFunc['db_free_result']($request);
4108
		return false;
4109
	}
4110
4111
	$validationResult = $smcFunc['db_fetch_assoc']($request);
4112
	$smcFunc['db_free_result']($request);
4113
4114
	switch ($validFor)
4115
	{
4116
		case 'inbox':
4117
			return !empty($validationResult['valid_for_inbox']);
4118
			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...
4119
4120
		case 'outbox':
4121
			return !empty($validationResult['valid_for_outbox']);
4122
			break;
4123
4124
		case 'in_or_outbox':
4125
			return !empty($validationResult['valid_for_inbox']) || !empty($validationResult['valid_for_outbox']);
4126
			break;
4127
4128
		default:
4129
			trigger_error('Undefined validation type given', E_USER_ERROR);
4130
			break;
4131
	}
4132
}
4133
4134
?>