Completed
Push — release-2.1 ( 99ca30...c081b8 )
by Michael
07:09
created

PersonalMessage.php ➔ messageIndexBar()   D

Complexity

Conditions 13
Paths 72

Size

Total Lines 143
Code Lines 87

Duplication

Lines 14
Ratio 9.79 %

Importance

Changes 0
Metric Value
cc 13
eloc 87
nc 72
nop 1
dl 14
loc 143
rs 4.9922
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 2017 Simple Machines and individual contributors
13
 * @license http://www.simplemachines.org/about/smf/license.php BSD
14
 *
15
 * @version 2.1 Beta 3
16
 */
17
18
if (!defined('SMF'))
19
	die('No direct access...');
20
21
/**
22
 * This helps organize things...
23
 * @todo this should be a simple dispatcher....
24
 */
25
function MessageMain()
26
{
27
	global $txt, $scripturl, $sourcedir, $context, $user_info, $user_settings, $smcFunc, $modSettings;
28
29
	// No guests!
30
	is_not_guest();
31
32
	// You're not supposed to be here at all, if you can't even read PMs.
33
	isAllowedTo('pm_read');
34
35
	// This file contains the basic functions for sending a PM.
36
	require_once($sourcedir . '/Subs-Post.php');
37
38
	loadLanguage('PersonalMessage+Drafts');
39
40
	if (!isset($_REQUEST['xml']))
41
		loadTemplate('PersonalMessage');
42
43
	// Load up the members maximum message capacity.
44
	if ($user_info['is_admin'])
45
		$context['message_limit'] = 0;
46
	elseif (($context['message_limit'] = cache_get_data('msgLimit:' . $user_info['id'], 360)) === null)
47
	{
48
		// @todo Why do we do this?  It seems like if they have any limit we should use it.
49
		$request = $smcFunc['db_query']('', '
50
			SELECT MAX(max_messages) AS top_limit, MIN(max_messages) AS bottom_limit
51
			FROM {db_prefix}membergroups
52
			WHERE id_group IN ({array_int:users_groups})',
53
			array(
54
				'users_groups' => $user_info['groups'],
55
			)
56
		);
57
		list ($maxMessage, $minMessage) = $smcFunc['db_fetch_row']($request);
58
		$smcFunc['db_free_result']($request);
59
60
		$context['message_limit'] = $minMessage == 0 ? 0 : $maxMessage;
61
62
		// Save us doing it again!
63
		cache_put_data('msgLimit:' . $user_info['id'], $context['message_limit'], 360);
64
	}
65
66
	// Prepare the context for the capacity bar.
67 View Code Duplication
	if (!empty($context['message_limit']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
68
	{
69
		$bar = ($user_info['messages'] * 100) / $context['message_limit'];
70
71
		$context['limit_bar'] = array(
72
			'messages' => $user_info['messages'],
73
			'allowed' => $context['message_limit'],
74
			'percent' => $bar,
75
			'bar' => min(100, (int) $bar),
76
			'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], round($bar, 1)),
77
		);
78
	}
79
80
	// a previous message was sent successfully? show a small indication.
81
	if (isset($_GET['done']) && ($_GET['done'] == 'sent'))
82
		$context['pm_sent'] = true;
83
84
	$context['labels'] = array();
85
86
	// Load the label data.
87
	if ($user_settings['new_pm'] || ($context['labels'] = cache_get_data('labelCounts:' . $user_info['id'], 720)) === null)
88
	{
89
		// Looks like we need to reseek!
90
91
		// Inbox "label"
92
		$context['labels'][-1] = array(
93
			'id' => -1,
94
			'name' => $txt['pm_msg_label_inbox'],
95
			'messages' => 0,
96
			'unread_messages' => 0,
97
		);
98
99
		// First get the inbox counts
100
		// The CASE WHEN here is because is_read is set to 3 when you reply to a message
101
		$result = $smcFunc['db_query']('', '
102
			SELECT COUNT(*) AS total, SUM(is_read & 1) AS num_read
103
			FROM {db_prefix}pm_recipients
104
			WHERE id_member = {int:current_member}
105
				AND in_inbox = {int:in_inbox}
106
				AND deleted = {int:not_deleted}',
107
			array(
108
				'current_member' => $user_info['id'],
109
				'in_inbox' => 1,
110
				'not_deleted' => 0,
111
			)
112
		);
113
114
		while ($row = $smcFunc['db_fetch_assoc']($result))
115
		{
116
			$context['labels'][-1]['messages'] = $row['total'];
117
			$context['labels'][-1]['unread_messages'] = $row['total'] - $row['num_read'];
118
		}
119
120
		$smcFunc['db_free_result']($result);
121
122
		// Now load info about all the other labels
123
		$result = $smcFunc['db_query']('', '
124
			SELECT l.id_label, l.name, COALESCE(SUM(pr.is_read & 1), 0) AS num_read, COALESCE(COUNT(pr.id_pm), 0) AS total
125
			FROM {db_prefix}pm_labels AS l
126
				LEFT JOIN {db_prefix}pm_labeled_messages AS pl ON (pl.id_label = l.id_label)
127
				LEFT JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pl.id_pm)
128
			WHERE l.id_member = {int:current_member}
129
			GROUP BY l.id_label, l.name',
130
			array(
131
				'current_member' => $user_info['id'],
132
			)
133
		);
134
135
		while ($row = $smcFunc['db_fetch_assoc']($result))
136
		{
137
			$context['labels'][$row['id_label']] = array(
138
				'id' => $row['id_label'],
139
				'name' => $row['name'],
140
				'messages' => $row['total'],
141
				'unread_messages' => $row['total'] - $row['num_read']
142
			);
143
		}
144
145
		$smcFunc['db_free_result']($result);
146
147
		// Store it please!
148
		cache_put_data('labelCounts:' . $user_info['id'], $context['labels'], 720);
149
	}
150
151
	// Now we have the labels, and assuming we have unsorted mail, apply our rules!
152
	if ($user_settings['new_pm'])
153
	{
154
		ApplyRules();
155
		updateMemberData($user_info['id'], array('new_pm' => 0));
156
		$smcFunc['db_query']('', '
157
			UPDATE {db_prefix}pm_recipients
158
			SET is_new = {int:not_new}
159
			WHERE id_member = {int:current_member}',
160
			array(
161
				'current_member' => $user_info['id'],
162
				'not_new' => 0,
163
			)
164
		);
165
	}
166
167
	// This determines if we have more labels than just the standard inbox.
168
	$context['currently_using_labels'] = count($context['labels']) > 1 ? 1 : 0;
169
170
	// Some stuff for the labels...
171
	$context['current_label_id'] = isset($_REQUEST['l']) && isset($context['labels'][$_REQUEST['l']]) ? (int) $_REQUEST['l'] : -1;
172
	$context['current_label'] = &$context['labels'][$context['current_label_id']]['name'];
173
	$context['folder'] = !isset($_REQUEST['f']) || $_REQUEST['f'] != 'sent' ? 'inbox' : 'sent';
174
175
	// This is convenient.  Do you know how annoying it is to do this every time?!
176
	$context['current_label_redirect'] = 'action=pm;f=' . $context['folder'] . (isset($_GET['start']) ? ';start=' . $_GET['start'] : '') . (isset($_REQUEST['l']) ? ';l=' . $_REQUEST['l'] : '');
177
	$context['can_issue_warning'] = allowedTo('issue_warning') && $modSettings['warning_settings'][0] == 1;
178
179
	// Are PM drafts enabled?
180
	$context['drafts_pm_save'] = !empty($modSettings['drafts_pm_enabled']) && allowedTo('pm_draft');
181
	$context['drafts_autosave'] = !empty($context['drafts_pm_save']) && !empty($modSettings['drafts_autosave_enabled']);
182
183
	// Build the linktree for all the actions...
184
	$context['linktree'][] = array(
185
		'url' => $scripturl . '?action=pm',
186
		'name' => $txt['personal_messages']
187
	);
188
189
	// Preferences...
190
	$context['display_mode'] = $user_settings['pm_prefs'] & 3;
191
192
	$subActions = array(
193
		'popup' => 'MessagePopup',
194
		'manlabels' => 'ManageLabels',
195
		'manrules' => 'ManageRules',
196
		'pmactions' => 'MessageActionsApply',
197
		'prune' => 'MessagePrune',
198
		'removeall' => 'MessageKillAllQuery',
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 View Code Duplication
	else
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
				'send' => array(
237
					'label' => $txt['new_message'],
238
					'custom_url' => $scripturl . '?action=pm;sa=send',
239
					'permission' => allowedTo('pm_send'),
240
				),
241
				'inbox' => array(
242
					'label' => $txt['inbox'],
243
					'custom_url' => $scripturl . '?action=pm',
244
				),
245
				'sent' => array(
246
					'label' => $txt['sent_items'],
247
					'custom_url' => $scripturl . '?action=pm;f=sent',
248
				),
249
				'drafts' => array(
250
					'label' => $txt['drafts_show'],
251
					'custom_url' => $scripturl . '?action=pm;sa=showpmdrafts',
252
					'permission' => allowedTo('pm_draft'),
253
					'enabled' => !empty($modSettings['drafts_pm_enabled']),
254
				),
255
			),
256
		),
257
		'labels' => array(
258
			'title' => $txt['pm_labels'],
259
			'areas' => array(),
260
		),
261
		'actions' => array(
262
			'title' => $txt['pm_actions'],
263
			'areas' => array(
264
				'search' => array(
265
					'label' => $txt['pm_search_bar_title'],
266
					'custom_url' => $scripturl . '?action=pm;sa=search',
267
				),
268
				'prune' => array(
269
					'label' => $txt['pm_prune'],
270
					'custom_url' => $scripturl . '?action=pm;sa=prune'
271
				),
272
			),
273
		),
274
		'pref' => array(
275
			'title' => $txt['pm_preferences'],
276
			'areas' => array(
277
				'manlabels' => array(
278
					'label' => $txt['pm_manage_labels'],
279
					'custom_url' => $scripturl . '?action=pm;sa=manlabels',
280
				),
281
				'manrules' => array(
282
					'label' => $txt['pm_manage_rules'],
283
					'custom_url' => $scripturl . '?action=pm;sa=manrules',
284
				),
285
				'settings' => array(
286
					'label' => $txt['pm_settings'],
287
					'custom_url' => $scripturl . '?action=pm;sa=settings',
288
				),
289
			),
290
		),
291
	);
292
293
	// Handle labels.
294
	if (empty($context['currently_using_labels']))
295
		unset($pm_areas['labels']);
296
	else
297
	{
298
		// Note we send labels by id as it will have less problems in the querystring.
299
		$unread_in_labels = 0;
300
		foreach ($context['labels'] as $label)
301
		{
302
			if ($label['id'] == -1)
303
				continue;
304
305
			// Count the amount of unread items in labels.
306
			$unread_in_labels += $label['unread_messages'];
307
308
			// Add the label to the menu.
309
			$pm_areas['labels']['areas']['label' . $label['id']] = array(
310
				'label' => $label['name'] . (!empty($label['unread_messages']) ? ' <span class="amt">' . $label['unread_messages'] . '</span>' : ''),
311
				'custom_url' => $scripturl . '?action=pm;l=' . $label['id'],
312
				'unread_messages' => $label['unread_messages'],
313
				'messages' => $label['messages'],
314
			);
315
		}
316
317
		if (!empty($unread_in_labels))
318
			$pm_areas['labels']['title'] .= ' <span class="amt">' . $unread_in_labels . '</span>';
319
	}
320
321
	$pm_areas['folders']['areas']['inbox']['unread_messages'] = &$context['labels'][-1]['unread_messages'];
322
	$pm_areas['folders']['areas']['inbox']['messages'] = &$context['labels'][-1]['messages'];
323
	if (!empty($context['labels'][-1]['unread_messages']))
324
	{
325
		$pm_areas['folders']['areas']['inbox']['label'] .= ' <span class="amt">' . $context['labels'][-1]['unread_messages'] . '</span>';
326
		$pm_areas['folders']['title'] .= ' <span class="amt">' . $context['labels'][-1]['unread_messages'] . '</span>';
327
	}
328
329
	// Do we have a limit on the amount of messages we can keep?
330 View Code Duplication
	if (!empty($context['message_limit']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
331
	{
332
		$bar = round(($user_info['messages'] * 100) / $context['message_limit'], 1);
333
334
		$context['limit_bar'] = array(
335
			'messages' => $user_info['messages'],
336
			'allowed' => $context['message_limit'],
337
			'percent' => $bar,
338
			'bar' => $bar > 100 ? 100 : (int) $bar,
339
			'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], $bar)
340
		);
341
	}
342
343
	require_once($sourcedir . '/Subs-Menu.php');
344
345
	// Set a few options for the menu.
346
	$menuOptions = array(
347
		'current_area' => $area,
348
		'disable_url_session_check' => true,
349
	);
350
351
	// Actually create the menu!
352
	$pm_include_data = createMenu($pm_areas, $menuOptions);
353
	unset($pm_areas);
354
355
	// No menu means no access.
356 View Code Duplication
	if (!$pm_include_data && (!$user_info['is_guest'] || validateSession()))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
357
		fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
358
359
	// Make a note of the Unique ID for this menu.
360
	$context['pm_menu_id'] = $context['max_menu_id'];
361
	$context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id'];
362
363
	// Set the selected item.
364
	$current_area = $pm_include_data['current_area'];
365
	$context['menu_item_selected'] = $current_area;
366
367
	// Set the template for this area and add the profile layer.
368
	if (!isset($_REQUEST['xml']))
369
		$context['template_layers'][] = 'pm';
370
}
371
372
/**
373
 * The popup for when we ask for the popup from the user.
374
 */
375
function MessagePopup()
376
{
377
	global $context, $modSettings, $smcFunc, $memberContext, $scripturl, $user_settings, $db_show_debug;
378
379
	// We do not want to output debug information here.
380
	$db_show_debug = false;
381
382
	// We only want to output our little layer here.
383
	$context['template_layers'] = array();
384
	$context['sub_template'] = 'pm_popup';
385
386
	$context['can_send_pm'] = allowedTo('pm_send');
387
	$context['can_draft'] = allowedTo('pm_draft') && !empty($modSettings['drafts_pm_enabled']);
388
389
	// So are we loading stuff?
390
	$request = $smcFunc['db_query']('', '
391
		SELECT id_pm
392
		FROM {db_prefix}pm_recipients AS pmr
393
		WHERE pmr.id_member = {int:current_member}
394
			AND is_read = {int:not_read}
395
		ORDER BY id_pm',
396
		array(
397
			'current_member' => $context['user']['id'],
398
			'not_read' => 0,
399
		)
400
	);
401
	$pms = array();
402
	while ($row = $smcFunc['db_fetch_row']($request))
403
		$pms[] = $row[0];
404
	$smcFunc['db_free_result']($request);
405
406
	if (!empty($pms))
407
	{
408
		// Just quickly, it's possible that the number of PMs can get out of sync.
409
		$count_unread = count($pms);
410
		if ($count_unread != $user_settings['unread_messages'])
411
		{
412
			updateMemberData($context['user']['id'], array('unread_messages' => $count_unread));
413
			$context['user']['unread_messages'] = count($pms);
414
		}
415
416
		// Now, actually fetch me some PMs. Make sure we track the senders, got some work to do for them.
417
		$senders = array();
418
419
		$request = $smcFunc['db_query']('', '
420
			SELECT pm.id_pm, pm.id_pm_head, COALESCE(mem.id_member, pm.id_member_from) AS id_member_from,
421
				COALESCE(mem.real_name, pm.from_name) AS member_from, pm.msgtime AS timestamp, pm.subject
422
			FROM {db_prefix}personal_messages AS pm
423
				LEFT JOIN {db_prefix}members AS mem ON (pm.id_member_from = mem.id_member)
424
			WHERE pm.id_pm IN ({array_int:id_pms})',
425
			array(
426
				'id_pms' => $pms,
427
			)
428
		);
429
		while ($row = $smcFunc['db_fetch_assoc']($request))
430
		{
431
			if (!empty($row['id_member_from']))
432
				$senders[] = $row['id_member_from'];
433
434
			$row['replied_to_you'] = $row['id_pm'] != $row['id_pm_head'];
435
			$row['time'] = timeformat($row['timestamp']);
436
			$row['pm_link'] = '<a href="' . $scripturl . '?action=pm;f=inbox;pmsg=' . $row['id_pm'] . '">' . $row['subject'] . '</a>';
437
			$context['unread_pms'][$row['id_pm']] = $row;
438
		}
439
		$smcFunc['db_free_result']($request);
440
441
		$senders = loadMemberData($senders);
442
		foreach ($senders as $member)
443
			loadMemberContext($member);
444
445
		// Having loaded everyone, attach them to the PMs.
446
		foreach ($context['unread_pms'] as $id_pm => $details)
447
			if (!empty($memberContext[$details['id_member_from']]))
448
				$context['unread_pms'][$id_pm]['member'] = &$memberContext[$details['id_member_from']];
449
	}
450
}
451
452
/**
453
 * A folder, ie. inbox/sent etc.
454
 */
455
function MessageFolder()
456
{
457
	global $txt, $scripturl, $modSettings, $context, $subjects_request;
458
	global $messages_request, $user_info, $recipients, $options, $smcFunc, $user_settings;
459
460
	// Changing view?
461
	if (isset($_GET['view']))
462
	{
463
		$context['display_mode'] = $context['display_mode'] > 1 ? 0 : $context['display_mode'] + 1;
464
		updateMemberData($user_info['id'], array('pm_prefs' => ($user_settings['pm_prefs'] & 252) | $context['display_mode']));
465
	}
466
467
	// Make sure the starting location is valid.
468
	if (isset($_GET['start']) && $_GET['start'] != 'new')
469
		$_GET['start'] = (int) $_GET['start'];
470
	elseif (!isset($_GET['start']) && !empty($options['view_newest_pm_first']))
471
		$_GET['start'] = 0;
472
	else
473
		$_GET['start'] = 'new';
474
475
	// Set up some basic theme stuff.
476
	$context['from_or_to'] = $context['folder'] != 'sent' ? 'from' : 'to';
477
	$context['get_pmessage'] = 'prepareMessageContext';
478
	$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
479
	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
480
481
	$labelJoin = '';
482
	$labelQuery = '';
483
	$labelQuery2 = '';
484
485
	// SMF logic: If you're viewing a label, it's still the inbox
486
	if ($context['folder'] == 'inbox' && $context['current_label_id'] == -1)
487
	{
488
		$labelQuery = '
489
			AND pmr.in_inbox = 1';
490
	}
491
	elseif ($context['folder'] != 'sent')
492
	{
493
		$labelJoin = '
494
			INNER JOIN {db_prefix}pm_labeled_messages AS pl ON (pl.id_pm = pmr.id_pm)';
495
496
		$labelQuery2 = '
497
			AND pl.id_label = ' . $context['current_label_id'];
498
	}
499
500
	// Set the index bar correct!
501
	messageIndexBar($context['current_label_id'] == -1 ? $context['folder'] : 'label' . $context['current_label_id']);
502
503
	// Sorting the folder.
504
	$sort_methods = array(
505
		'date' => 'pm.id_pm',
506
		'name' => 'COALESCE(mem.real_name, \'\')',
507
		'subject' => 'pm.subject',
508
	);
509
510
	// They didn't pick one, use the forum default.
511
	if (!isset($_GET['sort']) || !isset($sort_methods[$_GET['sort']]))
512
	{
513
		$context['sort_by'] = 'date';
514
		$_GET['sort'] = 'pm.id_pm';
515
		// An overriding setting?
516
		$descending = !empty($options['view_newest_pm_first']);
517
	}
518
	// Otherwise use the defaults: ascending, by date.
519 View Code Duplication
	else
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
520
	{
521
		$context['sort_by'] = $_GET['sort'];
522
		$_GET['sort'] = $sort_methods[$_GET['sort']];
523
		$descending = isset($_GET['desc']);
524
	}
525
526
	$context['sort_direction'] = $descending ? 'down' : 'up';
527
528
	// Set the text to resemble the current folder.
529
	$pmbox = $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items'];
530
	$txt['delete_all'] = str_replace('PMBOX', $pmbox, $txt['delete_all']);
531
532
	// Now, build the link tree!
533
	if ($context['current_label_id'] == -1)
534
		$context['linktree'][] = array(
535
			'url' => $scripturl . '?action=pm;f=' . $context['folder'],
536
			'name' => $pmbox
537
		);
538
539
	// Build it further for a label.
540
	if ($context['current_label_id'] != -1)
541
		$context['linktree'][] = array(
542
			'url' => $scripturl . '?action=pm;f=' . $context['folder'] . ';l=' . $context['current_label_id'],
543
			'name' => $txt['pm_current_label'] . ': ' . $context['current_label']
544
		);
545
546
	// Figure out how many messages there are.
547
	if ($context['folder'] == 'sent')
548
		$request = $smcFunc['db_query']('', '
549
			SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
550
			FROM {db_prefix}personal_messages AS pm
551
			WHERE pm.id_member_from = {int:current_member}
552
				AND pm.deleted_by_sender = {int:not_deleted}',
553
			array(
554
				'current_member' => $user_info['id'],
555
				'not_deleted' => 0,
556
			)
557
		);
558
	else
559
		$request = $smcFunc['db_query']('', '
560
			SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
561
			FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
562
				INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . $labelJoin . '
563
			WHERE pmr.id_member = {int:current_member}
564
				AND pmr.deleted = {int:not_deleted}' . $labelQuery . $labelQuery2,
565
			array(
566
				'current_member' => $user_info['id'],
567
				'not_deleted' => 0,
568
			)
569
		);
570
	list ($max_messages) = $smcFunc['db_fetch_row']($request);
571
	$smcFunc['db_free_result']($request);
572
573
	// Only show the button if there are messages to delete.
574
	$context['show_delete'] = $max_messages > 0;
575
	$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
576
577
	// Start on the last page.
578
	if (!is_numeric($_GET['start']) || $_GET['start'] >= $max_messages)
579
		$_GET['start'] = ($max_messages - 1) - (($max_messages - 1) % $maxPerPage);
580
	elseif ($_GET['start'] < 0)
581
		$_GET['start'] = 0;
582
583
	// ... but wait - what if we want to start from a specific message?
584
	if (isset($_GET['pmid']))
585
	{
586
		$pmID = (int) $_GET['pmid'];
587
588
		// Make sure you have access to this PM.
589 View Code Duplication
		if (!isAccessiblePM($pmID, $context['folder'] == 'sent' ? 'outbox' : 'inbox'))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
			fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
591
592
		$context['current_pm'] = $pmID;
593
594
		// With only one page of PM's we're gonna want page 1.
595
		if ($max_messages <= $maxPerPage)
596
			$_GET['start'] = 0;
597
		// If we pass kstart we assume we're in the right place.
598
		elseif (!isset($_GET['kstart']))
599
		{
600
			if ($context['folder'] == 'sent')
601
				$request = $smcFunc['db_query']('', '
602
					SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
603
					FROM {db_prefix}personal_messages
604
					WHERE id_member_from = {int:current_member}
605
						AND deleted_by_sender = {int:not_deleted}
606
						AND id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}',
607
					array(
608
						'current_member' => $user_info['id'],
609
						'not_deleted' => 0,
610
						'id_pm' => $pmID,
611
					)
612
				);
613
			else
614
				$request = $smcFunc['db_query']('', '
615
					SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
616
					FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
617
						INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . $labelJoin . '
618
					WHERE pmr.id_member = {int:current_member}
619
						AND pmr.deleted = {int:not_deleted}' . $labelQuery . $labelQuery2 . '
620
						AND pmr.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
628
			list ($_GET['start']) = $smcFunc['db_fetch_row']($request);
629
			$smcFunc['db_free_result']($request);
630
631
			// To stop the page index's being abnormal, start the page on the page the message would normally be located on...
632
			$_GET['start'] = $maxPerPage * (int) ($_GET['start'] / $maxPerPage);
633
		}
634
	}
635
636
	// Sanitize and validate pmsg variable if set.
637
	if (isset($_GET['pmsg']))
638
	{
639
		$pmsg = (int) $_GET['pmsg'];
640
641 View Code Duplication
		if (!isAccessiblePM($pmsg, $context['folder'] == 'sent' ? 'outbox' : 'inbox'))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
642
			fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
643
	}
644
645
	// Set up the page index.
646
	$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);
647
	$context['start'] = $_GET['start'];
648
649
	// Determine the navigation context.
650
	$context['links'] = array(
651
		'first' => $_GET['start'] >= $maxPerPage ? $scripturl . '?action=pm;start=0' : '',
652
		'prev' => $_GET['start'] >= $maxPerPage ? $scripturl . '?action=pm;start=' . ($_GET['start'] - $maxPerPage) : '',
653
		'next' => $_GET['start'] + $maxPerPage < $max_messages ? $scripturl . '?action=pm;start=' . ($_GET['start'] + $maxPerPage) : '',
654
		'last' => $_GET['start'] + $maxPerPage < $max_messages ? $scripturl . '?action=pm;start=' . (floor(($max_messages - 1) / $maxPerPage) * $maxPerPage) : '',
655
		'up' => $scripturl,
656
	);
657
	$context['page_info'] = array(
658
		'current_page' => $_GET['start'] / $maxPerPage + 1,
659
		'num_pages' => floor(($max_messages - 1) / $maxPerPage) + 1
660
	);
661
662
	// First work out what messages we need to see - if grouped is a little trickier...
663
	if ($context['display_mode'] == 2)
664
	{
665
		if ($context['folder'] != 'sent' && $context['folder'] != 'inbox')
666
		{
667
			$labelJoin = '
668
				INNER JOIN {db_prefix}pm_labeled_messages AS pl ON (pl.id_pm = pm.id_pm)';
669
670
			$labelQuery = '';
671
			$labelQuery2 = '
672
				AND pl.id_label = ' . $context['current_label_id'];
673
		}
674
675
		$request = $smcFunc['db_query']('pm_conversation_list', '
676
				SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
677
				FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ($context['sort_by'] == 'name' ? '
678
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
679
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
680
					AND pmr.id_member = {int:current_member}
681
					AND pmr.deleted = {int:deleted_by}
682
					' . $labelQuery . ')') . $labelJoin . ($context['sort_by'] == 'name' ? ('
683
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
684
				WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member}
685
					AND pm.deleted_by_sender = {int:deleted_by}' : '1=1') . (empty($pmsg) ? '' : '
686
					AND pm.id_pm = {int:pmsg}') . $labelQuery2 . '
687
				GROUP BY pm.id_pm_head'.($_GET['sort'] != 'pm.id_pm' ? ',' . $_GET['sort'] : '') . '
688
				ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($_GET['pmsg']) ? '
689
				LIMIT ' . $_GET['start'] . ', ' . $maxPerPage : ''),
690
				array(
691
					'current_member' => $user_info['id'],
692
					'deleted_by' => 0,
693
					'sort' => $_GET['sort'],
694
					'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
695
					'pmsg' => isset($pmsg) ? (int) $pmsg : 0,
696
				)
697
		);
698
699
	}
700
	// This is kinda simple!
701
	else
702
	{
703
		// @todo SLOW This query uses a filesort. (inbox only.)
704
		$request = $smcFunc['db_query']('', '
705
			SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
706
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' . ($context['sort_by'] == 'name' ? '
707
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
708
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
709
					AND pmr.id_member = {int:current_member}
710
					AND pmr.deleted = {int:is_deleted}
711
					' . $labelQuery . ')') . $labelJoin . ($context['sort_by'] == 'name' ? ('
712
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
713
			WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {raw:current_member}
714
				AND pm.deleted_by_sender = {int:is_deleted}' : '1=1') . (empty($pmsg) ? '' : '
715
				AND pm.id_pm = {int:pmsg}') . $labelQuery2 . '
716
			ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'pmr.id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($pmsg) ? '
717
			LIMIT ' . $_GET['start'] . ', ' . $maxPerPage : ''),
718
			array(
719
				'current_member' => $user_info['id'],
720
				'is_deleted' => 0,
721
				'sort' => $_GET['sort'],
722
				'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
723
				'pmsg' => isset($pmsg) ? (int) $pmsg : 0,
724
			)
725
		);
726
	}
727
	// Load the id_pms and initialize recipients.
728
	$pms = array();
729
	$lastData = array();
730
	$posters = $context['folder'] == 'sent' ? array($user_info['id']) : array();
731
	$recipients = array();
732
733
	while ($row = $smcFunc['db_fetch_assoc']($request))
734
	{
735
		if (!isset($recipients[$row['id_pm']]))
736
		{
737
			if (isset($row['id_member_from']))
738
				$posters[$row['id_pm']] = $row['id_member_from'];
739
			$pms[$row['id_pm']] = $row['id_pm'];
740
			$recipients[$row['id_pm']] = array(
741
				'to' => array(),
742
				'bcc' => array()
743
			);
744
		}
745
746
		// Keep track of the last message so we know what the head is without another query!
747
		if ((empty($pmID) && (empty($options['view_newest_pm_first']) || !isset($lastData))) || empty($lastData) || (!empty($pmID) && $pmID == $row['id_pm']))
748
			$lastData = array(
749
				'id' => $row['id_pm'],
750
				'head' => $row['id_pm_head'],
751
			);
752
	}
753
	$smcFunc['db_free_result']($request);
754
755
	// Make sure that we have been given a correct head pm id!
756
	if ($context['display_mode'] == 2 && !empty($pmID) && $pmID != $lastData['id'])
757
		fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
758
759
	if (!empty($pms))
760
	{
761
		// Select the correct current message.
762
		if (empty($pmID))
763
			$context['current_pm'] = $lastData['id'];
764
765
		// This is a list of the pm's that are used for "full" display.
766
		if ($context['display_mode'] == 0)
767
			$display_pms = $pms;
768
		else
769
			$display_pms = array($context['current_pm']);
770
771
		// At this point we know the main id_pm's. But - if we are looking at conversations we need the others!
772
		if ($context['display_mode'] == 2)
773
		{
774
			$request = $smcFunc['db_query']('', '
775
				SELECT pm.id_pm, pm.id_member_from, pm.deleted_by_sender, pmr.id_member, pmr.deleted
776
				FROM {db_prefix}personal_messages AS pm
777
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
778
				WHERE pm.id_pm_head = {int:id_pm_head}
779
					AND ((pm.id_member_from = {int:current_member} AND pm.deleted_by_sender = {int:not_deleted})
780
						OR (pmr.id_member = {int:current_member} AND pmr.deleted = {int:not_deleted}))
781
				ORDER BY pm.id_pm',
782
				array(
783
					'current_member' => $user_info['id'],
784
					'id_pm_head' => $lastData['head'],
785
					'not_deleted' => 0,
786
				)
787
			);
788
			while ($row = $smcFunc['db_fetch_assoc']($request))
789
			{
790
				// This is, frankly, a joke. We will put in a workaround for people sending to themselves - yawn!
791
				if ($context['folder'] == 'sent' && $row['id_member_from'] == $user_info['id'] && $row['deleted_by_sender'] == 1)
792
					continue;
793
				elseif ($row['id_member'] == $user_info['id'] & $row['deleted'] == 1)
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: ($row['id_member'] == $u... & $row['deleted'] == 1, Probably Intended Meaning: $row['id_member'] == ($u...& $row['deleted'] == 1)

When comparing the result of a bit operation, we suggest to add explicit parenthesis and not to rely on PHP’s built-in operator precedence to ensure the code behaves as intended and to make it more readable.

Let’s take a look at these examples:

// Returns always int(0).
return 0 === $foo & 4;
return (0 === $foo) & 4;

// More likely intended return: true/false
return 0 === ($foo & 4);
Loading history...
794
					continue;
795
796
				if (!isset($recipients[$row['id_pm']]))
797
					$recipients[$row['id_pm']] = array(
798
						'to' => array(),
799
						'bcc' => array()
800
					);
801
				$display_pms[] = $row['id_pm'];
802
				$posters[$row['id_pm']] = $row['id_member_from'];
803
			}
804
			$smcFunc['db_free_result']($request);
805
		}
806
807
		// This is pretty much EVERY pm!
808
		$all_pms = array_merge($pms, $display_pms);
809
		$all_pms = array_unique($all_pms);
810
811
		// Get recipients (don't include bcc-recipients for your inbox, you're not supposed to know :P).
812
		$request = $smcFunc['db_query']('', '
813
			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
814
			FROM {db_prefix}pm_recipients AS pmr
815
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
816
			WHERE pmr.id_pm IN ({array_int:pm_list})',
817
			array(
818
				'pm_list' => $all_pms,
819
			)
820
		);
821
		$context['message_labels'] = array();
822
		$context['message_replied'] = array();
823
		$context['message_unread'] = array();
824
		while ($row = $smcFunc['db_fetch_assoc']($request))
825
		{
826
			if ($context['folder'] == 'sent' || empty($row['bcc']))
827
			{
828
				$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>';
829
830
				$context['folder'] == 'sent' && $context['display_mode'] != 2 ? $context['message_replied'][$row['id_pm']] = $row['is_read'] & 2 : '';
831
			}
832
833
			if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
834
			{
835
				$context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
836
				$context['message_unread'][$row['id_pm']] = $row['is_read'] == 0;
837
838
				// Get the labels for this PM
839
				$request2 = $smcFunc['db_query']('', '
840
					SELECT id_label
841
					FROM {db_prefix}pm_labeled_messages
842
					WHERE id_pm = {int:current_pm}',
843
					array(
844
						'current_pm' => $row['id_pm'],
845
					)
846
				);
847
848
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
849
				{
850
					$l_id = $row2['id_label'];
851 View Code Duplication
					if (isset($context['labels'][$l_id]))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
852
						$context['message_labels'][$row['id_pm']][$l_id] = array('id' => $l_id, 'name' => $context['labels'][$l_id]['name']);
853
				}
854
855
				$smcFunc['db_free_result']($request2);
856
857
				// Is this in the inbox as well?
858 View Code Duplication
				if ($row['in_inbox'] == 1)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
859
				{
860
					$context['message_labels'][$row['id_pm']][-1] = array('id' => -1, 'name' => $context['labels'][-1]['name']);
861
				}
862
			}
863
		}
864
		$smcFunc['db_free_result']($request);
865
866
		// Make sure we don't load unnecessary data.
867
		if ($context['display_mode'] == 1)
868
		{
869
			foreach ($posters as $k => $v)
870
				if (!in_array($k, $display_pms))
871
					unset($posters[$k]);
872
		}
873
874
		// Load any users....
875
		loadMemberData($posters);
876
877
		// If we're on grouped/restricted view get a restricted list of messages.
878
		if ($context['display_mode'] != 0)
879
		{
880
			// Get the order right.
881
			$orderBy = array();
882
			foreach (array_reverse($pms) as $pm)
883
				$orderBy[] = 'pm.id_pm = ' . $pm;
884
885
			// Seperate query for these bits!
886
			$subjects_request = $smcFunc['db_query']('', '
887
				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,
888
					mem.id_member
889
				FROM {db_prefix}personal_messages AS pm
890
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
891
				WHERE pm.id_pm IN ({array_int:pm_list})
892
				ORDER BY ' . implode(', ', $orderBy) . '
893
				LIMIT {int:limit}',
894
				array(
895
					'pm_list' => $pms,
896
					'limit' => count($pms),
897
				)
898
			);
899
		}
900
901
		// Execute the query!
902
		$messages_request = $smcFunc['db_query']('', '
903
			SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
904
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '
905
				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') . ($context['sort_by'] == 'name' ? '
906
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})' : '') . '
907
			WHERE pm.id_pm IN ({array_int:display_pms})' . ($context['folder'] == 'sent' ? '
908
			GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' : '') . '
909
			ORDER BY ' . ($context['display_mode'] == 2 ? 'pm.id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . '
910
			LIMIT {int:limit}',
911
			array(
912
				'display_pms' => $display_pms,
913
				'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
914
				'limit' => count($display_pms),
915
				'sort' => $_GET['sort'],
916
			)
917
		);
918
919
		// Build the conversation button array.
920
		if ($context['display_mode'] == 2)
921
		{
922
			$context['conversation_buttons'] = array(
923
				'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'),
924
			);
925
926
			// Allow mods to add additional buttons here
927
			call_integration_hook('integrate_conversation_buttons');
928
		}
929
	}
930
	else
931
		$messages_request = false;
932
933
	$context['can_send_pm'] = allowedTo('pm_send');
934
	$context['can_send_email'] = allowedTo('moderate_forum');
935
	$context['sub_template'] = 'folder';
936
	$context['page_title'] = $txt['pm_inbox'];
937
938
	// Finally mark the relevant messages as read.
939
	if ($context['folder'] != 'sent' && !empty($context['labels'][(int) $context['current_label_id']]['unread_messages']))
940
	{
941
		// If the display mode is "old sk00l" do them all...
942
		if ($context['display_mode'] == 0)
943
			markMessages(null, $context['current_label_id']);
944
		// Otherwise do just the current one!
945
		elseif (!empty($context['current_pm']))
946
			markMessages($display_pms, $context['current_label_id']);
0 ignored issues
show
Bug introduced by
The variable $display_pms does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
947
	}
948
}
949
950
/**
951
 * Get a personal message for the theme.  (used to save memory.)
952
 *
953
 * @param string $type The type of message
954
 * @param bool $reset Whether to reset the internal pointer
955
 * @return bool|array False on failure, otherwise an array of info
956
 */
957
function prepareMessageContext($type = 'subject', $reset = false)
958
{
959
	global $txt, $scripturl, $modSettings, $context, $messages_request, $memberContext, $recipients, $smcFunc;
960
	global $user_info, $subjects_request;
961
962
	// Count the current message number....
963
	static $counter = null;
964
	if ($counter === null || $reset)
965
		$counter = $context['start'];
966
967
	static $temp_pm_selected = null;
968
	if ($temp_pm_selected === null)
969
	{
970
		$temp_pm_selected = isset($_SESSION['pm_selected']) ? $_SESSION['pm_selected'] : array();
971
		$_SESSION['pm_selected'] = array();
972
	}
973
974
	// If we're in non-boring view do something exciting!
975
	if ($context['display_mode'] != 0 && $subjects_request && $type == 'subject')
976
	{
977
		$subject = $smcFunc['db_fetch_assoc']($subjects_request);
978
		if (!$subject)
979
		{
980
			$smcFunc['db_free_result']($subjects_request);
981
			return false;
982
		}
983
984
		$subject['subject'] = $subject['subject'] == '' ? $txt['no_subject'] : $subject['subject'];
985
		censorText($subject['subject']);
986
987
		$output = array(
988
			'id' => $subject['id_pm'],
989
			'member' => array(
990
				'id' => $subject['id_member_from'],
991
				'name' => $subject['from_name'],
992
				'link' => ($subject['id_member_from'] != 0) ? '<a href="' . $scripturl . '?action=profile;u=' . $subject['id_member_from'] . '">' . $subject['from_name'] . '</a>' : $subject['from_name'],
993
			),
994
			'recipients' => &$recipients[$subject['id_pm']],
995
			'subject' => $subject['subject'],
996
			'time' => timeformat($subject['msgtime']),
997
			'timestamp' => forum_time(true, $subject['msgtime']),
998
			'number_recipients' => count($recipients[$subject['id_pm']]['to']),
999
			'labels' => &$context['message_labels'][$subject['id_pm']],
1000
			'fully_labeled' => count($context['message_labels'][$subject['id_pm']]) == count($context['labels']),
1001
			'is_replied_to' => &$context['message_replied'][$subject['id_pm']],
1002
			'is_unread' => &$context['message_unread'][$subject['id_pm']],
1003
			'is_selected' => !empty($temp_pm_selected) && in_array($subject['id_pm'], $temp_pm_selected),
1004
		);
1005
1006
		return $output;
1007
	}
1008
1009
	// Bail if it's false, ie. no messages.
1010
	if ($messages_request == false)
1011
		return false;
1012
1013
	// Reset the data?
1014
	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...
1015
		return @$smcFunc['db_data_seek']($messages_request, 0);
1016
1017
	// Get the next one... bail if anything goes wrong.
1018
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1019
	if (!$message)
1020
	{
1021
		if ($type != 'subject')
1022
			$smcFunc['db_free_result']($messages_request);
1023
1024
		return false;
1025
	}
1026
1027
	// Use '(no subject)' if none was specified.
1028
	$message['subject'] = $message['subject'] == '' ? $txt['no_subject'] : $message['subject'];
1029
1030
	// Load the message's information - if it's not there, load the guest information.
1031
	if (!loadMemberContext($message['id_member_from'], true))
1032
	{
1033
		$memberContext[$message['id_member_from']]['name'] = $message['from_name'];
1034
		$memberContext[$message['id_member_from']]['id'] = 0;
1035
1036
		// Sometimes the forum sends messages itself (Warnings are an example) - in this case don't label it from a guest.
1037
		$memberContext[$message['id_member_from']]['group'] = $message['from_name'] == $context['forum_name_html_safe'] ? '' : $txt['guest_title'];
1038
		$memberContext[$message['id_member_from']]['link'] = $message['from_name'];
1039
		$memberContext[$message['id_member_from']]['email'] = '';
1040
		$memberContext[$message['id_member_from']]['show_email'] = false;
1041
		$memberContext[$message['id_member_from']]['is_guest'] = true;
1042
	}
1043
	else
1044
	{
1045
		$memberContext[$message['id_member_from']]['can_view_profile'] = allowedTo('profile_view') || ($message['id_member_from'] == $user_info['id'] && !$user_info['is_guest']);
1046
		$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'])));
1047
		// Show the email if it's your own PM
1048
		$memberContext[$message['id_member_from']]['show_email'] |= $message['id_member_from'] == $user_info['id'];
1049
	}
1050
1051
	$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']);
1052
1053
	// Censor all the important text...
1054
	censorText($message['body']);
1055
	censorText($message['subject']);
1056
1057
	// Run UBBC interpreter on the message.
1058
	$message['body'] = parse_bbc($message['body'], true, 'pm' . $message['id_pm']);
1059
1060
	// Send the array.
1061
	$output = array(
1062
		'id' => $message['id_pm'],
1063
		'member' => &$memberContext[$message['id_member_from']],
1064
		'subject' => $message['subject'],
1065
		'time' => timeformat($message['msgtime']),
1066
		'timestamp' => forum_time(true, $message['msgtime']),
1067
		'counter' => $counter,
1068
		'body' => $message['body'],
1069
		'recipients' => &$recipients[$message['id_pm']],
1070
		'number_recipients' => count($recipients[$message['id_pm']]['to']),
1071
		'labels' => &$context['message_labels'][$message['id_pm']],
1072
		'fully_labeled' => count($context['message_labels'][$message['id_pm']]) == count($context['labels']),
1073
		'is_replied_to' => &$context['message_replied'][$message['id_pm']],
1074
		'is_unread' => &$context['message_unread'][$message['id_pm']],
1075
		'is_selected' => !empty($temp_pm_selected) && in_array($message['id_pm'], $temp_pm_selected),
1076
		'is_message_author' => $message['id_member_from'] == $user_info['id'],
1077
		'can_report' => !empty($modSettings['enableReportPM']),
1078
		'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member_from'] == $user_info['id'] && !empty($user_info['id'])),
1079
	);
1080
1081
	$counter++;
1082
1083
	// Any custom profile fields?
1084
	if (!empty($memberContext[$message['id_member_from']]['custom_fields']))
1085
		foreach ($memberContext[$message['id_member_from']]['custom_fields'] as $custom)
1086
			switch ($custom['placement'])
1087
			{
1088
				case 1:
1089
					$output['custom_fields']['icons'][] = $custom;
1090
					break;
1091
				case 2:
1092
					$output['custom_fields']['above_signature'][] = $custom;
1093
					break;
1094
				case 3:
1095
					$output['custom_fields']['below_signature'][] = $custom;
1096
					break;
1097
				case 4:
1098
					$output['custom_fields']['below_avatar'][] = $custom;
1099
					break;
1100
				case 5:
1101
					$output['custom_fields']['above_member'][] = $custom;
1102
					break;
1103
				case 6:
1104
					$output['custom_fields']['bottom_poster'][] = $custom;
1105
					break;
1106
				default:
1107
					$output['custom_fields']['standard'][] = $custom;
1108
			}
1109
1110
	call_integration_hook('integrate_prepare_pm_context', array(&$output, &$message, $counter));
1111
1112
	return $output;
1113
}
1114
1115
/**
1116
 * Allows searching through personal messages.
1117
 */
1118
function MessageSearch()
1119
{
1120
	global $context, $txt, $scripturl, $smcFunc;
1121
1122
	if (isset($_REQUEST['params']))
1123
	{
1124
		$temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
1125
		$context['search_params'] = array();
1126 View Code Duplication
		foreach ($temp_params as $i => $data)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1127
		{
1128
			@list ($k, $v) = explode('|\'|', $data);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1129
			$context['search_params'][$k] = $v;
1130
		}
1131
	}
1132 View Code Duplication
	if (isset($_REQUEST['search']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1133
		$context['search_params']['search'] = un_htmlspecialchars($_REQUEST['search']);
1134
1135 View Code Duplication
	if (isset($context['search_params']['search']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1136
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
1137 View Code Duplication
	if (isset($context['search_params']['userspec']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1138
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
1139
1140
	if (!empty($context['search_params']['searchtype']))
1141
		$context['search_params']['searchtype'] = 2;
1142
1143 View Code Duplication
	if (!empty($context['search_params']['minage']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1144
		$context['search_params']['minage'] = (int) $context['search_params']['minage'];
1145
1146 View Code Duplication
	if (!empty($context['search_params']['maxage']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1147
		$context['search_params']['maxage'] = (int) $context['search_params']['maxage'];
1148
1149
	$context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']);
1150
	$context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']);
1151
1152
	// Create the array of labels to be searched.
1153
	$context['search_labels'] = array();
1154
	$searchedLabels = isset($context['search_params']['labels']) && $context['search_params']['labels'] != '' ? explode(',', $context['search_params']['labels']) : array();
1155
	foreach ($context['labels'] as $label)
1156
	{
1157
		$context['search_labels'][] = array(
1158
			'id' => $label['id'],
1159
			'name' => $label['name'],
1160
			'checked' => !empty($searchedLabels) ? in_array($label['id'], $searchedLabels) : true,
1161
		);
1162
	}
1163
1164
	// Are all the labels checked?
1165
	$context['check_all'] = empty($searchedLabels) || count($context['search_labels']) == count($searchedLabels);
1166
1167
	// Load the error text strings if there were errors in the search.
1168
	if (!empty($context['search_errors']))
1169
	{
1170
		loadLanguage('Errors');
1171
		$context['search_errors']['messages'] = array();
1172
		foreach ($context['search_errors'] as $search_error => $dummy)
0 ignored issues
show
Bug introduced by
The expression $context['search_errors'] of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1173
		{
1174
			if ($search_error == 'messages')
1175
				continue;
1176
1177
			$context['search_errors']['messages'][] = $txt['error_' . $search_error];
1178
		}
1179
	}
1180
1181
	$context['page_title'] = $txt['pm_search_title'];
1182
	$context['sub_template'] = 'search';
1183
	$context['linktree'][] = array(
1184
		'url' => $scripturl . '?action=pm;sa=search',
1185
		'name' => $txt['pm_search_bar_title'],
1186
	);
1187
}
1188
1189
/**
1190
 * Actually do the search of personal messages.
1191
 */
1192
function MessageSearch2()
1193
{
1194
	global $scripturl, $modSettings, $user_info, $context, $txt;
1195
	global $memberContext, $smcFunc;
1196
1197 View Code Duplication
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search'])
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1198
		fatal_lang_error('loadavg_search_disabled', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1199
1200
	/**
1201
	 * @todo For the moment force the folder to the inbox.
1202
	 * @todo Maybe set the inbox based on a cookie or theme setting?
1203
	 */
1204
	$context['folder'] = 'inbox';
1205
1206
	// Some useful general permissions.
1207
	$context['can_send_pm'] = allowedTo('pm_send');
1208
1209
	// Some hardcoded veriables that can be tweaked if required.
1210
	$maxMembersToSearch = 500;
1211
1212
	// Extract all the search parameters.
1213
	$search_params = array();
1214
	if (isset($_REQUEST['params']))
1215
	{
1216
		$temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
1217 View Code Duplication
		foreach ($temp_params as $i => $data)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1218
		{
1219
			@list ($k, $v) = explode('|\'|', $data);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1220
			$search_params[$k] = $v;
1221
		}
1222
	}
1223
1224
	$context['start'] = isset($_GET['start']) ? (int) $_GET['start'] : 0;
1225
1226
	// Store whether simple search was used (needed if the user wants to do another query).
1227 View Code Duplication
	if (!isset($search_params['advanced']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1228
		$search_params['advanced'] = empty($_REQUEST['advanced']) ? 0 : 1;
1229
1230
	// 1 => 'allwords' (default, don't set as param) / 2 => 'anywords'.
1231 View Code Duplication
	if (!empty($search_params['searchtype']) || (!empty($_REQUEST['searchtype']) && $_REQUEST['searchtype'] == 2))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1232
		$search_params['searchtype'] = 2;
1233
1234
	// Minimum age of messages. Default to zero (don't set param in that case).
1235 View Code Duplication
	if (!empty($search_params['minage']) || (!empty($_REQUEST['minage']) && $_REQUEST['minage'] > 0))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1236
		$search_params['minage'] = !empty($search_params['minage']) ? (int) $search_params['minage'] : (int) $_REQUEST['minage'];
1237
1238
	// Maximum age of messages. Default to infinite (9999 days: param not set).
1239 View Code Duplication
	if (!empty($search_params['maxage']) || (!empty($_REQUEST['maxage']) && $_REQUEST['maxage'] != 9999))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1240
		$search_params['maxage'] = !empty($search_params['maxage']) ? (int) $search_params['maxage'] : (int) $_REQUEST['maxage'];
1241
1242
	$search_params['subject_only'] = !empty($search_params['subject_only']) || !empty($_REQUEST['subject_only']);
1243
	$search_params['show_complete'] = !empty($search_params['show_complete']) || !empty($_REQUEST['show_complete']);
1244
1245
	// Default the user name to a wildcard matching every user (*).
1246 View Code Duplication
	if (!empty($search_params['user_spec']) || (!empty($_REQUEST['userspec']) && $_REQUEST['userspec'] != '*'))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1247
		$search_params['userspec'] = isset($search_params['userspec']) ? $search_params['userspec'] : $_REQUEST['userspec'];
1248
1249
	// This will be full of all kinds of parameters!
1250
	$searchq_parameters = array();
1251
1252
	// If there's no specific user, then don't mention it in the main query.
1253
	if (empty($search_params['userspec']))
1254
		$userQuery = '';
1255
	else
1256
	{
1257
		$userString = strtr($smcFunc['htmlspecialchars']($search_params['userspec'], ENT_QUOTES), array('&quot;' => '"'));
1258
		$userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_'));
1259
1260
		preg_match_all('~"([^"]+)"~', $userString, $matches);
1261
		$possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString)));
1262
1263 View Code Duplication
		for ($k = 0, $n = count($possible_users); $k < $n; $k++)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1264
		{
1265
			$possible_users[$k] = trim($possible_users[$k]);
1266
1267
			if (strlen($possible_users[$k]) == 0)
1268
				unset($possible_users[$k]);
1269
		}
1270
1271
		if (!empty($possible_users))
1272
		{
1273
			// We need to bring this into the query and do it nice and cleanly.
1274
			$where_params = array();
1275
			$where_clause = array();
1276
			foreach ($possible_users as $k => $v)
1277
			{
1278
				$where_params['name_' . $k] = $v;
1279
				$where_clause[] = '{raw:real_name} LIKE {string:name_' . $k . '}';
1280
				if (!isset($where_params['real_name']))
1281
					$where_params['real_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name';
1282
			}
1283
1284
			// Who matches those criteria?
1285
			// @todo This doesn't support sent item searching.
1286
			$request = $smcFunc['db_query']('', '
1287
				SELECT id_member
1288
				FROM {db_prefix}members
1289
				WHERE ' . implode(' OR ', $where_clause),
1290
				$where_params
1291
			);
1292
1293
			// Simply do nothing if there're too many members matching the criteria.
1294
			if ($smcFunc['db_num_rows']($request) > $maxMembersToSearch)
1295
				$userQuery = '';
1296
			elseif ($smcFunc['db_num_rows']($request) == 0)
1297
			{
1298
				$userQuery = 'AND pm.id_member_from = 0 AND ({raw:pm_from_name} LIKE {raw:guest_user_name_implode})';
1299
				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
1300
				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
1301
			}
1302
			else
1303
			{
1304
				$memberlist = array();
1305
				while ($row = $smcFunc['db_fetch_assoc']($request))
1306
					$memberlist[] = $row['id_member'];
1307
				$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})))';
1308
				$searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name') . ' LIKE \'', $possible_users) . '\'';
1309
				$searchq_parameters['member_list'] = $memberlist;
1310
				$searchq_parameters['pm_from_name'] = $smcFunc['db_case_sensitive'] ? 'LOWER(pm.from_name)' : 'pm.from_name';
1311
			}
1312
			$smcFunc['db_free_result']($request);
1313
		}
1314
		else
1315
			$userQuery = '';
1316
	}
1317
1318
	// Setup the sorting variables...
1319
	// @todo Add more in here!
1320
	$sort_columns = array(
1321
		'pm.id_pm',
1322
	);
1323 View Code Duplication
	if (empty($search_params['sort']) && !empty($_REQUEST['sort']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1324
		list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, '');
1325
	$search_params['sort'] = !empty($search_params['sort']) && in_array($search_params['sort'], $sort_columns) ? $search_params['sort'] : 'pm.id_pm';
1326
	$search_params['sort_dir'] = !empty($search_params['sort_dir']) && $search_params['sort_dir'] == 'asc' ? 'asc' : 'desc';
1327
1328
	// Sort out any labels we may be searching by.
1329
	$labelQuery = '';
1330
	$labelJoin = '';
1331
	if ($context['folder'] == 'inbox' && !empty($search_params['advanced']) && $context['currently_using_labels'])
1332
	{
1333
		// Came here from pagination?  Put them back into $_REQUEST for sanitization.
1334
		if (isset($search_params['labels']))
1335
			$_REQUEST['searchlabel'] = explode(',', $search_params['labels']);
1336
1337
		// Assuming we have some labels - make them all integers.
1338
		if (!empty($_REQUEST['searchlabel']) && is_array($_REQUEST['searchlabel']))
1339
		{
1340
			foreach ($_REQUEST['searchlabel'] as $key => $id)
1341
				$_REQUEST['searchlabel'][$key] = (int) $id;
1342
		}
1343
		else
1344
			$_REQUEST['searchlabel'] = array();
1345
1346
		// Now that everything is cleaned up a bit, make the labels a param.
1347
		$search_params['labels'] = implode(',', $_REQUEST['searchlabel']);
1348
1349
		// No labels selected? That must be an error!
1350
		if (empty($_REQUEST['searchlabel']))
1351
			$context['search_errors']['no_labels_selected'] = true;
1352
		// Otherwise prepare the query!
1353
		elseif (count($_REQUEST['searchlabel']) != count($context['labels']))
1354
		{
1355
			// Special case here... "inbox" isn't a real label anymore...
1356
			if (in_array(-1, $_REQUEST['searchlabel']))
1357
			{
1358
				$labelQuery = '	AND pmr.in_inbox = {int:in_inbox}';
1359
				$searchq_parameters['in_inbox'] = 1;
1360
1361
				// Now we get rid of that...
1362
				$temp = array_diff($_REQUEST['searchlabel'], array(-1));
1363
				$_REQUEST['searchlabel'] = $temp;
1364
			}
1365
1366
			// Still have something?
1367
			if (!empty($_REQUEST['searchlabel']))
1368
			{
1369
				if ($labelQuery == '')
1370
				{
1371
					// Not searching the inbox - PM must be labeled
1372
					$labelQuery = ' AND pml.id_label IN ({array_int:labels})';
1373
					$labelJoin = ' INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1374
				}
1375
				else
1376
				{
1377
					// Searching the inbox - PM doesn't have to be labeled
1378
					$labelQuery = ' AND (' . substr($labelQuery, 5) . ' OR pml.id_label IN ({array_int:labels}))';
1379
					$labelJoin = ' LEFT JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)';
1380
				}
1381
1382
				$searchq_parameters['labels'] = $_REQUEST['searchlabel'];
1383
			}
1384
		}
1385
	}
1386
1387
	// What are we actually searching for?
1388
	$search_params['search'] = !empty($search_params['search']) ? $search_params['search'] : (isset($_REQUEST['search']) ? $_REQUEST['search'] : '');
1389
	// If we ain't got nothing - we should error!
1390
	if (!isset($search_params['search']) || $search_params['search'] == '')
1391
		$context['search_errors']['invalid_search_string'] = true;
1392
1393
	// Extract phrase parts first (e.g. some words "this is a phrase" some more words.)
1394
	preg_match_all('~(?:^|\s)([-]?)"([^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), $search_params['search'], $matches, PREG_PATTERN_ORDER);
1395
	$searchArray = $matches[2];
1396
1397
	// Remove the phrase parts and extract the words.
1398
	$tempSearch = explode(' ', preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']));
1399
1400
	// A minus sign in front of a word excludes the word.... so...
1401
	$excludedWords = array();
1402
1403
	// .. first, we check for things like -"some words", but not "-some words".
1404 View Code Duplication
	foreach ($matches[1] as $index => $word)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1405
		if ($word == '-')
1406
		{
1407
			$word = $smcFunc['strtolower'](trim($searchArray[$index]));
1408
			if (strlen($word) > 0)
1409
				$excludedWords[] = $word;
1410
			unset($searchArray[$index]);
1411
		}
1412
1413
	// Now we look for -test, etc.... normaller.
1414
	foreach ($tempSearch as $index => $word)
1415
	{
1416
		if (strpos(trim($word), '-') === 0)
1417
		{
1418
			$word = substr($smcFunc['strtolower']($word), 1);
1419
			if (strlen($word) > 0)
1420
				$excludedWords[] = $word;
1421
			unset($tempSearch[$index]);
1422
		}
1423
	}
1424
1425
	$searchArray = array_merge($searchArray, $tempSearch);
1426
1427
	// Trim everything and make sure there are no words that are the same.
1428
	foreach ($searchArray as $index => $value)
1429
	{
1430
		$searchArray[$index] = $smcFunc['strtolower'](trim($value));
1431
		if ($searchArray[$index] == '')
1432
			unset($searchArray[$index]);
1433
		else
1434
		{
1435
			// Sort out entities first.
1436
			$searchArray[$index] = $smcFunc['htmlspecialchars']($searchArray[$index]);
1437
		}
1438
	}
1439
	$searchArray = array_unique($searchArray);
1440
1441
	// Create an array of replacements for highlighting.
1442
	$context['mark'] = array();
1443
	foreach ($searchArray as $word)
1444
		$context['mark'][$word] = '<strong class="highlight">' . $word . '</strong>';
1445
1446
	// This contains *everything*
1447
	$searchWords = array_merge($searchArray, $excludedWords);
1448
1449
	// Make sure at least one word is being searched for.
1450
	if (empty($searchArray))
1451
		$context['search_errors']['invalid_search_string'] = true;
1452
1453
	// Sort out the search query so the user can edit it - if they want.
1454
	$context['search_params'] = $search_params;
1455 View Code Duplication
	if (isset($context['search_params']['search']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1456
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
1457 View Code Duplication
	if (isset($context['search_params']['userspec']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1458
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
1459
1460
	// Now we have all the parameters, combine them together for pagination and the like...
1461
	$context['params'] = array();
1462
	foreach ($search_params as $k => $v)
1463
		$context['params'][] = $k . '|\'|' . $v;
1464
	$context['params'] = base64_encode(implode('|"|', $context['params']));
1465
1466
	// Compile the subject query part.
1467
	$andQueryParts = array();
1468
1469
	foreach ($searchWords as $index => $word)
1470
	{
1471
		if ($word == '')
1472
			continue;
1473
1474
		if ($search_params['subject_only'])
1475
			$andQueryParts[] = 'pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '}';
1476
		else
1477
			$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 . '})';
1478
		$searchq_parameters['search_' . $index] = '%' . strtr($word, array('_' => '\\_', '%' => '\\%')) . '%';
1479
	}
1480
1481
	$searchQuery = ' 1=1';
1482
	if (!empty($andQueryParts))
1483
		$searchQuery = implode(!empty($search_params['searchtype']) && $search_params['searchtype'] == 2 ? ' OR ' : ' AND ', $andQueryParts);
1484
1485
	// Age limits?
1486
	$timeQuery = '';
1487
	if (!empty($search_params['minage']))
1488
		$timeQuery .= ' AND pm.msgtime < ' . (time() - $search_params['minage'] * 86400);
1489
	if (!empty($search_params['maxage']))
1490
		$timeQuery .= ' AND pm.msgtime > ' . (time() - $search_params['maxage'] * 86400);
1491
1492
	// If we have errors - return back to the first screen...
1493 View Code Duplication
	if (!empty($context['search_errors']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1494
	{
1495
		$_REQUEST['params'] = $context['params'];
1496
		return MessageSearch();
1497
	}
1498
1499
	// Get the amount of results.
1500
	$request = $smcFunc['db_query']('', '
1501
		SELECT COUNT(*)
1502
		FROM {db_prefix}pm_recipients AS pmr
1503
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1504
			' . $labelJoin . '
1505
		WHERE ' . ($context['folder'] == 'inbox' ? '
1506
			pmr.id_member = {int:current_member}
1507
			AND pmr.deleted = {int:not_deleted}' : '
1508
			pm.id_member_from = {int:current_member}
1509
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1510
			' . $userQuery . $labelQuery . $timeQuery . '
1511
			AND (' . $searchQuery . ')',
1512
		array_merge($searchq_parameters, array(
1513
			'current_member' => $user_info['id'],
1514
			'not_deleted' => 0,
1515
		))
1516
	);
1517
	list ($numResults) = $smcFunc['db_fetch_row']($request);
1518
	$smcFunc['db_free_result']($request);
1519
1520
	// Get all the matching messages... using standard search only (No caching and the like!)
1521
	// @todo This doesn't support sent item searching yet.
1522
	$request = $smcFunc['db_query']('', '
1523
		SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
1524
		FROM {db_prefix}pm_recipients AS pmr
1525
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1526
			' . $labelJoin . '
1527
		WHERE ' . ($context['folder'] == 'inbox' ? '
1528
			pmr.id_member = {int:current_member}
1529
			AND pmr.deleted = {int:not_deleted}' : '
1530
			pm.id_member_from = {int:current_member}
1531
			AND pm.deleted_by_sender = {int:not_deleted}') . '
1532
			' . $userQuery . $labelQuery . $timeQuery . '
1533
			AND (' . $searchQuery . ')
1534
		ORDER BY {raw:sort} {raw:sort_dir}
1535
		LIMIT {int:start}, {int:max}',
1536
		array_merge($searchq_parameters, array(
1537
			'current_member' => $user_info['id'],
1538
			'not_deleted' => 0,
1539
			'sort' => $search_params['sort'],
1540
			'sort_dir' => $search_params['sort_dir'],
1541
			'start' => $context['start'],
1542
			'max' => $modSettings['search_results_per_page'],
1543
		))
1544
	);
1545
	$foundMessages = array();
1546
	$posters = array();
1547
	$head_pms = array();
1548
	while ($row = $smcFunc['db_fetch_assoc']($request))
1549
	{
1550
		$foundMessages[] = $row['id_pm'];
1551
		$posters[] = $row['id_member_from'];
1552
		$head_pms[$row['id_pm']] = $row['id_pm_head'];
1553
	}
1554
	$smcFunc['db_free_result']($request);
1555
1556
	// Find the real head pms!
1557
	if ($context['display_mode'] == 2 && !empty($head_pms))
1558
	{
1559
		$request = $smcFunc['db_query']('', '
1560
			SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
1561
			FROM {db_prefix}personal_messages AS pm
1562
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
1563
			WHERE pm.id_pm_head IN ({array_int:head_pms})
1564
				AND pmr.id_member = {int:current_member}
1565
				AND pmr.deleted = {int:not_deleted}
1566
			GROUP BY pm.id_pm_head
1567
			LIMIT {int:limit}',
1568
			array(
1569
				'head_pms' => array_unique($head_pms),
1570
				'current_member' => $user_info['id'],
1571
				'not_deleted' => 0,
1572
				'limit' => count($head_pms),
1573
			)
1574
		);
1575
		$real_pm_ids = array();
1576 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1577
			$real_pm_ids[$row['id_pm_head']] = $row['id_pm'];
1578
		$smcFunc['db_free_result']($request);
1579
	}
1580
1581
	// Load the users...
1582
	loadMemberData($posters);
1583
1584
	// Sort out the page index.
1585
	$context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=search2;params=' . $context['params'], $_GET['start'], $numResults, $modSettings['search_results_per_page'], false);
1586
1587
	$context['message_labels'] = array();
1588
	$context['message_replied'] = array();
1589
	$context['personal_messages'] = array();
1590
1591
	if (!empty($foundMessages))
1592
	{
1593
		// Now get recipients (but don't include bcc-recipients for your inbox, you're not supposed to know :P!)
1594
		$request = $smcFunc['db_query']('', '
1595
			SELECT
1596
				pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name,
1597
				pmr.bcc, pmr.in_inbox, pmr.is_read
1598
			FROM {db_prefix}pm_recipients AS pmr
1599
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
1600
			WHERE pmr.id_pm IN ({array_int:message_list})',
1601
			array(
1602
				'message_list' => $foundMessages,
1603
			)
1604
		);
1605
		while ($row = $smcFunc['db_fetch_assoc']($request))
1606
		{
1607
			if ($context['folder'] == 'sent' || empty($row['bcc']))
1608
				$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>';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$recipients was never initialized. Although not strictly required by PHP, it is generally a good practice to add $recipients = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1609
1610
			if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
1611
			{
1612
				$context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
1613
1614
				$row['labels'] = '';
1615
1616
				// Get the labels for this PM
1617
				$request2 = $smcFunc['db_query']('', '
1618
					SELECT id_label
1619
					FROM {db_prefix}pm_labeled_messages
1620
					WHERE id_pm = {int:current_pm}',
1621
					array(
1622
						'current_pm' => $row['id_pm'],
1623
					)
1624
				);
1625
1626
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
1627
				{
1628
					$l_id = $row2['id_label'];
1629 View Code Duplication
					if (isset($context['labels'][$l_id]))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1630
						$context['message_labels'][$row['id_pm']][$l_id] = array('id' => $l_id, 'name' => $context['labels'][$l_id]['name']);
1631
1632
					// Here we find the first label on a message - for linking to posts in results
1633
					if (!isset($context['first_label'][$row['id_pm']]) && $row['in_inbox'] != 1)
1634
						$context['first_label'][$row['id_pm']] = $l_id;
1635
				}
1636
1637
				$smcFunc['db_free_result']($request2);
1638
1639
				// Is this in the inbox as well?
1640 View Code Duplication
				if ($row['in_inbox'] == 1)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1641
				{
1642
					$context['message_labels'][$row['id_pm']][-1] = array('id' => -1, 'name' => $context['labels'][-1]['name']);
1643
				}
1644
1645
				$row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']);
1646
			}
1647
		}
1648
1649
		// Prepare the query for the callback!
1650
		$request = $smcFunc['db_query']('', '
1651
			SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
1652
			FROM {db_prefix}personal_messages AS pm
1653
			WHERE pm.id_pm IN ({array_int:message_list})
1654
			ORDER BY {raw:sort} {raw:sort_dir}
1655
			LIMIT {int:limit}',
1656
			array(
1657
				'message_list' => $foundMessages,
1658
				'limit' => count($foundMessages),
1659
				'sort' => $search_params['sort'],
1660
				'sort_dir' => $search_params['sort_dir'],
1661
			)
1662
		);
1663
		$counter = 0;
1664
		while ($row = $smcFunc['db_fetch_assoc']($request))
1665
		{
1666
			// If there's no message subject, use the default.
1667
			$row['subject'] = $row['subject'] == '' ? $txt['no_subject'] : $row['subject'];
1668
1669
			// Load this posters context info, if it ain't there then fill in the essentials...
1670
			if (!loadMemberContext($row['id_member_from'], true))
1671
			{
1672
				$memberContext[$row['id_member_from']]['name'] = $row['from_name'];
1673
				$memberContext[$row['id_member_from']]['id'] = 0;
1674
				$memberContext[$row['id_member_from']]['group'] = $txt['guest_title'];
1675
				$memberContext[$row['id_member_from']]['link'] = $row['from_name'];
1676
				$memberContext[$row['id_member_from']]['email'] = '';
1677
				$memberContext[$row['id_member_from']]['is_guest'] = true;
1678
			}
1679
1680
			// Censor anything we don't want to see...
1681
			censorText($row['body']);
1682
			censorText($row['subject']);
1683
1684
			// Parse out any BBC...
1685
			$row['body'] = parse_bbc($row['body'], true, 'pm' . $row['id_pm']);
1686
1687
			$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'];
1688
			$context['personal_messages'][] = array(
1689
				'id' => $row['id_pm'],
1690
				'member' => &$memberContext[$row['id_member_from']],
1691
				'subject' => $row['subject'],
1692
				'body' => $row['body'],
1693
				'time' => timeformat($row['msgtime']),
1694
				'recipients' => &$recipients[$row['id_pm']],
0 ignored issues
show
Bug introduced by
The variable $recipients does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1695
				'labels' => &$context['message_labels'][$row['id_pm']],
1696
				'fully_labeled' => count($context['message_labels'][$row['id_pm']]) == count($context['labels']),
1697
				'is_replied_to' => &$context['message_replied'][$row['id_pm']],
1698
				'href' => $href,
1699
				'link' => '<a href="' . $href . '">' . $row['subject'] . '</a>',
1700
				'counter' => ++$counter,
1701
			);
1702
		}
1703
		$smcFunc['db_free_result']($request);
1704
	}
1705
1706
	call_integration_hook('integrate_search_pm_context');
1707
1708
	// Finish off the context.
1709
	$context['page_title'] = $txt['pm_search_title'];
1710
	$context['sub_template'] = 'search_results';
1711
	$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'search';
1712
	$context['linktree'][] = array(
1713
		'url' => $scripturl . '?action=pm;sa=search',
1714
		'name' => $txt['pm_search_bar_title'],
1715
	);
1716
}
1717
1718
/**
1719
 * Send a new message?
1720
 */
1721
function MessagePost()
1722
{
1723
	global $txt, $sourcedir, $scripturl, $modSettings;
1724
	global $context, $smcFunc, $language, $user_info;
1725
1726
	isAllowedTo('pm_send');
1727
1728
	loadLanguage('PersonalMessage');
1729
	// Just in case it was loaded from somewhere else.
1730
	loadTemplate('PersonalMessage');
1731
	loadJavaScriptFile('PersonalMessage.js', array('defer' => false), 'smf_pms');
1732
	loadJavaScriptFile('suggest.js', array('defer' => false), 'smf_suggest');
1733
	$context['sub_template'] = 'send';
1734
1735
	// Extract out the spam settings - cause it's neat.
1736
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
1737
1738
	// Set the title...
1739
	$context['page_title'] = $txt['send_message'];
1740
1741
	$context['reply'] = isset($_REQUEST['pmsg']) || isset($_REQUEST['quote']);
1742
1743
	// Check whether we've gone over the limit of messages we can send per hour.
1744
	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')
1745
	{
1746
		// How many messages have they sent this last hour?
1747
		$request = $smcFunc['db_query']('', '
1748
			SELECT COUNT(pr.id_pm) AS post_count
1749
			FROM {db_prefix}personal_messages AS pm
1750
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
1751
			WHERE pm.id_member_from = {int:current_member}
1752
				AND pm.msgtime > {int:msgtime}',
1753
			array(
1754
				'current_member' => $user_info['id'],
1755
				'msgtime' => time() - 3600,
1756
			)
1757
		);
1758
		list ($postCount) = $smcFunc['db_fetch_row']($request);
1759
		$smcFunc['db_free_result']($request);
1760
1761
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
1762
			fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1763
	}
1764
1765
	// Quoting/Replying to a message?
1766
	if (!empty($_REQUEST['pmsg']))
1767
	{
1768
		$pmsg = (int) $_REQUEST['pmsg'];
1769
1770
		// Make sure this is yours.
1771
		if (!isAccessiblePM($pmsg))
1772
			fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1773
1774
		// Work out whether this is one you've received?
1775
		$request = $smcFunc['db_query']('', '
1776
			SELECT
1777
				id_pm
1778
			FROM {db_prefix}pm_recipients
1779
			WHERE id_pm = {int:id_pm}
1780
				AND id_member = {int:current_member}
1781
			LIMIT 1',
1782
			array(
1783
				'current_member' => $user_info['id'],
1784
				'id_pm' => $pmsg,
1785
			)
1786
		);
1787
		$isReceived = $smcFunc['db_num_rows']($request) != 0;
1788
		$smcFunc['db_free_result']($request);
1789
1790
		// Get the quoted message (and make sure you're allowed to see this quote!).
1791
		$request = $smcFunc['db_query']('', '
1792
			SELECT
1793
				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,
1794
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
1795
				COALESCE(mem.real_name, pm.from_name) AS real_name
1796
			FROM {db_prefix}personal_messages AS pm' . (!$isReceived ? '' : '
1797
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:id_pm})') . '
1798
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
1799
			WHERE pm.id_pm = {int:id_pm}' . (!$isReceived ? '
1800
				AND pm.id_member_from = {int:current_member}' : '
1801
				AND pmr.id_member = {int:current_member}') . '
1802
			LIMIT 1',
1803
			array(
1804
				'current_member' => $user_info['id'],
1805
				'id_pm_head_empty' => 0,
1806
				'id_pm' => $pmsg,
1807
			)
1808
		);
1809
		if ($smcFunc['db_num_rows']($request) == 0)
1810
			fatal_lang_error('pm_not_yours', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1811
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
1812
		$smcFunc['db_free_result']($request);
1813
1814
		// Censor the message.
1815
		censorText($row_quoted['subject']);
1816
		censorText($row_quoted['body']);
1817
1818
		// Add 'Re: ' to it....
1819 View Code Duplication
		if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix')))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1820
		{
1821
			if ($language === $user_info['language'])
1822
				$context['response_prefix'] = $txt['response_prefix'];
1823
			else
1824
			{
1825
				loadLanguage('index', $language, false);
1826
				$context['response_prefix'] = $txt['response_prefix'];
1827
				loadLanguage('index');
1828
			}
1829
			cache_put_data('response_prefix', $context['response_prefix'], 600);
1830
		}
1831
		$form_subject = $row_quoted['subject'];
1832 View Code Duplication
		if ($context['reply'] && trim($context['response_prefix']) != '' && $smcFunc['strpos']($form_subject, trim($context['response_prefix'])) !== 0)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1833
			$form_subject = $context['response_prefix'] . $form_subject;
1834
1835
		if (isset($_REQUEST['quote']))
1836
		{
1837
			// Remove any nested quotes and <br>...
1838
			$form_message = preg_replace('~<br ?/?' . '>~i', "\n", $row_quoted['body']);
1839 View Code Duplication
			if (!empty($modSettings['removeNestedQuotes']))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1840
				$form_message = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $form_message);
1841
			if (empty($row_quoted['id_member']))
1842
				$form_message = '[quote author=&quot;' . $row_quoted['real_name'] . '&quot;]' . "\n" . $form_message . "\n" . '[/quote]';
1843
			else
1844
				$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]';
1845
		}
1846
		else
1847
			$form_message = '';
1848
1849
		// Do the BBC thang on the message.
1850
		$row_quoted['body'] = parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']);
1851
1852
		// Set up the quoted message array.
1853
		$context['quoted_message'] = array(
1854
			'id' => $row_quoted['id_pm'],
1855
			'pm_head' => $row_quoted['pm_head'],
1856
			'member' => array(
1857
				'name' => $row_quoted['real_name'],
1858
				'username' => $row_quoted['member_name'],
1859
				'id' => $row_quoted['id_member'],
1860
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
1861
				'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'],
1862
			),
1863
			'subject' => $row_quoted['subject'],
1864
			'time' => timeformat($row_quoted['msgtime']),
1865
			'timestamp' => forum_time(true, $row_quoted['msgtime']),
1866
			'body' => $row_quoted['body']
1867
		);
1868
	}
1869
	else
1870
	{
1871
		$context['quoted_message'] = false;
1872
		$form_subject = '';
1873
		$form_message = '';
1874
	}
1875
1876
	$context['recipients'] = array(
1877
		'to' => array(),
1878
		'bcc' => array(),
1879
	);
1880
1881
	// Sending by ID?  Replying to all?  Fetch the real_name(s).
1882
	if (isset($_REQUEST['u']))
1883
	{
1884
		// If the user is replying to all, get all the other members this was sent to..
1885
		if ($_REQUEST['u'] == 'all' && isset($row_quoted))
1886
		{
1887
			// Firstly, to reply to all we clearly already have $row_quoted - so have the original member from.
1888
			if ($row_quoted['id_member'] != $user_info['id'])
1889
				$context['recipients']['to'][] = array(
1890
					'id' => $row_quoted['id_member'],
1891
					'name' => $smcFunc['htmlspecialchars']($row_quoted['real_name']),
1892
				);
1893
1894
			// Now to get the others.
1895
			$request = $smcFunc['db_query']('', '
1896
				SELECT mem.id_member, mem.real_name
1897
				FROM {db_prefix}pm_recipients AS pmr
1898
					INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
1899
				WHERE pmr.id_pm = {int:id_pm}
1900
					AND pmr.id_member != {int:current_member}
1901
					AND pmr.bcc = {int:not_bcc}',
1902
				array(
1903
					'current_member' => $user_info['id'],
1904
					'id_pm' => $pmsg,
0 ignored issues
show
Bug introduced by
The variable $pmsg does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1905
					'not_bcc' => 0,
1906
				)
1907
			);
1908
			while ($row = $smcFunc['db_fetch_assoc']($request))
1909
				$context['recipients']['to'][] = array(
1910
					'id' => $row['id_member'],
1911
					'name' => $row['real_name'],
1912
				);
1913
			$smcFunc['db_free_result']($request);
1914
		}
1915
		else
1916
		{
1917
			$_REQUEST['u'] = explode(',', $_REQUEST['u']);
1918
			foreach ($_REQUEST['u'] as $key => $uID)
1919
				$_REQUEST['u'][$key] = (int) $uID;
1920
1921
			$_REQUEST['u'] = array_unique($_REQUEST['u']);
1922
1923
			$request = $smcFunc['db_query']('', '
1924
				SELECT id_member, real_name
1925
				FROM {db_prefix}members
1926
				WHERE id_member IN ({array_int:member_list})
1927
				LIMIT {int:limit}',
1928
				array(
1929
					'member_list' => $_REQUEST['u'],
1930
					'limit' => count($_REQUEST['u']),
1931
				)
1932
			);
1933
			while ($row = $smcFunc['db_fetch_assoc']($request))
1934
				$context['recipients']['to'][] = array(
1935
					'id' => $row['id_member'],
1936
					'name' => $row['real_name'],
1937
				);
1938
			$smcFunc['db_free_result']($request);
1939
		}
1940
1941
		// Get a literal name list in case the user has JavaScript disabled.
1942
		$names = array();
1943
		foreach ($context['recipients']['to'] as $to)
1944
			$names[] = $to['name'];
1945
		$context['to_value'] = empty($names) ? '' : '&quot;' . implode('&quot;, &quot;', $names) . '&quot;';
1946
	}
1947
	else
1948
		$context['to_value'] = '';
1949
1950
	// Set the defaults...
1951
	$context['subject'] = $form_subject;
1952
	$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
1953
	$context['post_error'] = array();
1954
1955
	// And build the link tree.
1956
	$context['linktree'][] = array(
1957
		'url' => $scripturl . '?action=pm;sa=send',
1958
		'name' => $txt['new_message']
1959
	);
1960
1961
	$modSettings['disable_wysiwyg'] = !empty($modSettings['disable_wysiwyg']) || empty($modSettings['enableBBC']);
1962
1963
	// Generate a list of drafts that they can load in to the editor
1964
	if (!empty($context['drafts_pm_save']))
1965
	{
1966
		require_once($sourcedir . '/Drafts.php');
1967
		$pm_seed = isset($_REQUEST['pmsg']) ? $_REQUEST['pmsg'] : (isset($_REQUEST['quote']) ? $_REQUEST['quote'] : 0);
1968
		ShowDrafts($user_info['id'], $pm_seed, 1);
0 ignored issues
show
Documentation introduced by
$pm_seed is of type array|integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1969
	}
1970
1971
	// Needed for the WYSIWYG editor.
1972
	require_once($sourcedir . '/Subs-Editor.php');
1973
1974
	// Now create the editor.
1975
	$editorOptions = array(
1976
		'id' => 'message',
1977
		'value' => $context['message'],
1978
		'height' => '250px',
1979
		'width' => '100%',
1980
		'labels' => array(
1981
			'post_button' => $txt['send_message'],
1982
		),
1983
		'preview_type' => 2,
1984
		'required' => true,
1985
	);
1986
	create_control_richedit($editorOptions);
1987
1988
	// Store the ID for old compatibility.
1989
	$context['post_box_name'] = $editorOptions['id'];
1990
1991
	$context['bcc_value'] = '';
1992
1993
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
1994 View Code Duplication
	if ($context['require_verification'])
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1995
	{
1996
		$verificationOptions = array(
1997
			'id' => 'pm',
1998
		);
1999
		$context['require_verification'] = create_control_verification($verificationOptions);
2000
		$context['visual_verification_id'] = $verificationOptions['id'];
2001
	}
2002
2003
	call_integration_hook('integrate_pm_post');
2004
2005
	// Register this form and get a sequence number in $context.
2006
	checkSubmitOnce('register');
2007
}
2008
2009
/**
2010
 * This function allows the user to view their PM drafts
2011
 */
2012
function MessageDrafts()
2013
{
2014
	global $sourcedir, $user_info;
2015
2016
	// validate with loadMemberData()
2017
	$memberResult = loadMemberData($user_info['id'], false);
2018
	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...
2019
		fatal_lang_error('not_a_user', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2020
	list ($memID) = $memberResult;
2021
2022
	// drafts is where the functions reside
2023
	require_once($sourcedir . '/Drafts.php');
2024
	showPMDrafts($memID);
2025
}
2026
2027
/**
2028
 * An error in the message...
2029
 *
2030
 * @param array $error_types An array of strings indicating which type of errors occurred
2031
 * @param array $named_recipients
2032
 * @param $recipient_ids
2033
 */
2034
function messagePostError($error_types, $named_recipients, $recipient_ids = array())
2035
{
2036
	global $txt, $context, $scripturl, $modSettings;
2037
	global $smcFunc, $user_info, $sourcedir;
2038
2039
	if (!isset($_REQUEST['xml']))
2040
	{
2041
		$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'send';
2042
		$context['sub_template'] = 'send';
2043
		loadJavaScriptFile('PersonalMessage.js', array('defer' => false), 'smf_pms');
2044
		loadJavaScriptFile('suggest.js', array('defer' => false), 'smf_suggest');
2045
	}
2046
	else
2047
		$context['sub_template'] = 'pm';
2048
2049
	$context['page_title'] = $txt['send_message'];
2050
2051
	// Got some known members?
2052
	$context['recipients'] = array(
2053
		'to' => array(),
2054
		'bcc' => array(),
2055
	);
2056
	if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc']))
2057
	{
2058
		$allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']);
2059
2060
		$request = $smcFunc['db_query']('', '
2061
			SELECT id_member, real_name
2062
			FROM {db_prefix}members
2063
			WHERE id_member IN ({array_int:member_list})',
2064
			array(
2065
				'member_list' => $allRecipients,
2066
			)
2067
		);
2068
		while ($row = $smcFunc['db_fetch_assoc']($request))
2069
		{
2070
			$recipientType = in_array($row['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to';
2071
			$context['recipients'][$recipientType][] = array(
2072
				'id' => $row['id_member'],
2073
				'name' => $row['real_name'],
2074
			);
2075
		}
2076
		$smcFunc['db_free_result']($request);
2077
	}
2078
2079
	// Set everything up like before....
2080
	$context['subject'] = isset($_REQUEST['subject']) ? $smcFunc['htmlspecialchars']($_REQUEST['subject']) : '';
2081
	$context['message'] = isset($_REQUEST['message']) ? str_replace(array('  '), array('&nbsp; '), $smcFunc['htmlspecialchars']($_REQUEST['message'])) : '';
2082
	$context['reply'] = !empty($_REQUEST['replied_to']);
2083
2084
	if ($context['reply'])
2085
	{
2086
		$_REQUEST['replied_to'] = (int) $_REQUEST['replied_to'];
2087
2088
		$request = $smcFunc['db_query']('', '
2089
			SELECT
2090
				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,
2091
				pm.body, pm.subject, pm.msgtime, mem.member_name, COALESCE(mem.id_member, 0) AS id_member,
2092
				COALESCE(mem.real_name, pm.from_name) AS real_name
2093
			FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' : '
2094
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:replied_to})') . '
2095
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
2096
			WHERE pm.id_pm = {int:replied_to}' . ($context['folder'] == 'sent' ? '
2097
				AND pm.id_member_from = {int:current_member}' : '
2098
				AND pmr.id_member = {int:current_member}') . '
2099
			LIMIT 1',
2100
			array(
2101
				'current_member' => $user_info['id'],
2102
				'no_id_pm_head' => 0,
2103
				'replied_to' => $_REQUEST['replied_to'],
2104
			)
2105
		);
2106
		if ($smcFunc['db_num_rows']($request) == 0)
2107
		{
2108
			if (!isset($_REQUEST['xml']))
2109
				fatal_lang_error('pm_not_yours', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2110
			else
2111
				$error_types[] = 'pm_not_yours';
2112
		}
2113
		$row_quoted = $smcFunc['db_fetch_assoc']($request);
2114
		$smcFunc['db_free_result']($request);
2115
2116
		censorText($row_quoted['subject']);
2117
		censorText($row_quoted['body']);
2118
2119
		$context['quoted_message'] = array(
2120
			'id' => $row_quoted['id_pm'],
2121
			'pm_head' => $row_quoted['pm_head'],
2122
			'member' => array(
2123
				'name' => $row_quoted['real_name'],
2124
				'username' => $row_quoted['member_name'],
2125
				'id' => $row_quoted['id_member'],
2126
				'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
2127
				'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'],
2128
			),
2129
			'subject' => $row_quoted['subject'],
2130
			'time' => timeformat($row_quoted['msgtime']),
2131
			'timestamp' => forum_time(true, $row_quoted['msgtime']),
2132
			'body' => parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']),
2133
		);
2134
	}
2135
2136
	// Build the link tree....
2137
	$context['linktree'][] = array(
2138
		'url' => $scripturl . '?action=pm;sa=send',
2139
		'name' => $txt['new_message']
2140
	);
2141
2142
	// Set each of the errors for the template.
2143
	loadLanguage('Errors');
2144
2145
	$context['error_type'] = 'minor';
2146
2147
	$context['post_error'] = array(
2148
		'messages' => array(),
2149
		// @todo error handling: maybe fatal errors can be error_type => serious
2150
		'error_type' => '',
2151
	);
2152
2153
	foreach ($error_types as $error_type)
2154
	{
2155
		$context['post_error'][$error_type] = true;
2156
		if (isset($txt['error_' . $error_type]))
2157
		{
2158
			if ($error_type == 'long_message')
2159
				$txt['error_' . $error_type] = sprintf($txt['error_' . $error_type], $modSettings['max_messageLength']);
2160
			$context['post_error']['messages'][] = $txt['error_' . $error_type];
2161
		}
2162
2163
		// If it's not a minor error flag it as such.
2164
		if (!in_array($error_type, array('new_reply', 'not_approved', 'new_replies', 'old_topic', 'need_qr_verification', 'no_subject')))
2165
			$context['error_type'] = 'serious';
2166
	}
2167
2168
	// We need to load the editor once more.
2169
	require_once($sourcedir . '/Subs-Editor.php');
2170
2171
	// Create it...
2172
	$editorOptions = array(
2173
		'id' => 'message',
2174
		'value' => $context['message'],
2175
		'width' => '90%',
2176
		'height' => '250px',
2177
		'labels' => array(
2178
			'post_button' => $txt['send_message'],
2179
		),
2180
		'preview_type' => 2,
2181
	);
2182
	create_control_richedit($editorOptions);
2183
2184
	// ... and store the ID again...
2185
	$context['post_box_name'] = $editorOptions['id'];
2186
2187
	// Check whether we need to show the code again.
2188
	$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
2189
	if ($context['require_verification'] && !isset($_REQUEST['xml']))
2190
	{
2191
		require_once($sourcedir . '/Subs-Editor.php');
2192
		$verificationOptions = array(
2193
			'id' => 'pm',
2194
		);
2195
		$context['require_verification'] = create_control_verification($verificationOptions);
2196
		$context['visual_verification_id'] = $verificationOptions['id'];
2197
	}
2198
2199
	$context['to_value'] = empty($named_recipients['to']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['to']) . '&quot;';
2200
	$context['bcc_value'] = empty($named_recipients['bcc']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['bcc']) . '&quot;';
2201
2202
	call_integration_hook('integrate_pm_error');
2203
2204
	// No check for the previous submission is needed.
2205
	checkSubmitOnce('free');
2206
2207
	// Acquire a new form sequence number.
2208
	checkSubmitOnce('register');
2209
}
2210
2211
/**
2212
 * Send it!
2213
 */
2214
function MessagePost2()
2215
{
2216
	global $txt, $context, $sourcedir;
2217
	global $user_info, $modSettings, $smcFunc;
2218
2219
	isAllowedTo('pm_send');
2220
	require_once($sourcedir . '/Subs-Auth.php');
2221
2222
	// PM Drafts enabled and needed?
2223
	if ($context['drafts_pm_save'] && (isset($_POST['save_draft']) || isset($_POST['id_pm_draft'])))
2224
		require_once($sourcedir . '/Drafts.php');
2225
2226
	loadLanguage('PersonalMessage', '', false);
2227
2228
	// Extract out the spam settings - it saves database space!
2229
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
2230
2231
	// Initialize the errors we're about to make.
2232
	$post_errors = array();
2233
2234
	// Check whether we've gone over the limit of messages we can send per hour - fatal error if fails!
2235
	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')
2236
	{
2237
		// How many have they sent this last hour?
2238
		$request = $smcFunc['db_query']('', '
2239
			SELECT COUNT(pr.id_pm) AS post_count
2240
			FROM {db_prefix}personal_messages AS pm
2241
				INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
2242
			WHERE pm.id_member_from = {int:current_member}
2243
				AND pm.msgtime > {int:msgtime}',
2244
			array(
2245
				'current_member' => $user_info['id'],
2246
				'msgtime' => time() - 3600,
2247
			)
2248
		);
2249
		list ($postCount) = $smcFunc['db_fetch_row']($request);
2250
		$smcFunc['db_free_result']($request);
2251
2252
		if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
2253
		{
2254
			if (!isset($_REQUEST['xml']))
2255
				fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2256
			else
2257
				$post_errors[] = 'pm_too_many_per_hour';
2258
		}
2259
	}
2260
2261
	// If your session timed out, show an error, but do allow to re-submit.
2262
	if (!isset($_REQUEST['xml']) && checkSession('post', '', false) != '')
2263
		$post_errors[] = 'session_timeout';
2264
2265
	$_REQUEST['subject'] = isset($_REQUEST['subject']) ? trim($_REQUEST['subject']) : '';
2266
	$_REQUEST['to'] = empty($_POST['to']) ? (empty($_GET['to']) ? '' : $_GET['to']) : $_POST['to'];
2267
	$_REQUEST['bcc'] = empty($_POST['bcc']) ? (empty($_GET['bcc']) ? '' : $_GET['bcc']) : $_POST['bcc'];
2268
2269
	// Route the input from the 'u' parameter to the 'to'-list.
2270
	if (!empty($_POST['u']))
2271
		$_POST['recipient_to'] = explode(',', $_POST['u']);
2272
2273
	// Construct the list of recipients.
2274
	$recipientList = array();
2275
	$namedRecipientList = array();
2276
	$namesNotFound = array();
2277
	foreach (array('to', 'bcc') as $recipientType)
2278
	{
2279
		// First, let's see if there's user ID's given.
2280
		$recipientList[$recipientType] = array();
2281
		if (!empty($_POST['recipient_' . $recipientType]) && is_array($_POST['recipient_' . $recipientType]))
2282
		{
2283
			foreach ($_POST['recipient_' . $recipientType] as $recipient)
2284
				$recipientList[$recipientType][] = (int) $recipient;
2285
		}
2286
2287
		// Are there also literal names set?
2288
		if (!empty($_REQUEST[$recipientType]))
2289
		{
2290
			// We're going to take out the "s anyway ;).
2291
			$recipientString = strtr($_REQUEST[$recipientType], array('\\"' => '"'));
2292
2293
			preg_match_all('~"([^"]+)"~', $recipientString, $matches);
2294
			$namedRecipientList[$recipientType] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))));
2295
2296 View Code Duplication
			foreach ($namedRecipientList[$recipientType] as $index => $recipient)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2297
			{
2298
				if (strlen(trim($recipient)) > 0)
2299
					$namedRecipientList[$recipientType][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($recipient)));
2300
				else
2301
					unset($namedRecipientList[$recipientType][$index]);
2302
			}
2303
2304
			if (!empty($namedRecipientList[$recipientType]))
2305
			{
2306
				$foundMembers = findMembers($namedRecipientList[$recipientType]);
2307
2308
				// Assume all are not found, until proven otherwise.
2309
				$namesNotFound[$recipientType] = $namedRecipientList[$recipientType];
2310
2311
				foreach ($foundMembers as $member)
2312
				{
2313
					$testNames = array(
2314
						$smcFunc['strtolower']($member['username']),
2315
						$smcFunc['strtolower']($member['name']),
2316
						$smcFunc['strtolower']($member['email']),
2317
					);
2318
2319
					if (count(array_intersect($testNames, $namedRecipientList[$recipientType])) !== 0)
2320
					{
2321
						$recipientList[$recipientType][] = $member['id'];
2322
2323
						// Get rid of this username, since we found it.
2324
						$namesNotFound[$recipientType] = array_diff($namesNotFound[$recipientType], $testNames);
2325
					}
2326
				}
2327
			}
2328
		}
2329
2330
		// Selected a recipient to be deleted? Remove them now.
2331
		if (!empty($_POST['delete_recipient']))
2332
			$recipientList[$recipientType] = array_diff($recipientList[$recipientType], array((int) $_POST['delete_recipient']));
2333
2334
		// Make sure we don't include the same name twice
2335
		$recipientList[$recipientType] = array_unique($recipientList[$recipientType]);
2336
	}
2337
2338
	// Are we changing the recipients some how?
2339
	$is_recipient_change = !empty($_POST['delete_recipient']) || !empty($_POST['to_submit']) || !empty($_POST['bcc_submit']);
2340
2341
	// Check if there's at least one recipient.
2342
	if (empty($recipientList['to']) && empty($recipientList['bcc']))
2343
		$post_errors[] = 'no_to';
2344
2345
	// Make sure that we remove the members who did get it from the screen.
2346
	if (!$is_recipient_change)
2347
	{
2348
		foreach ($recipientList as $recipientType => $dummy)
2349
		{
2350
			if (!empty($namesNotFound[$recipientType]))
2351
			{
2352
				$post_errors[] = 'bad_' . $recipientType;
2353
2354
				// Since we already have a post error, remove the previous one.
2355
				$post_errors = array_diff($post_errors, array('no_to'));
2356
2357
				foreach ($namesNotFound[$recipientType] as $name)
2358
					$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
2359
			}
2360
		}
2361
	}
2362
2363
	// Did they make any mistakes?
2364
	if ($_REQUEST['subject'] == '')
2365
		$post_errors[] = 'no_subject';
2366
	if (!isset($_REQUEST['message']) || $_REQUEST['message'] == '')
2367
		$post_errors[] = 'no_message';
2368 View Code Duplication
	elseif (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_REQUEST['message']) > $modSettings['max_messageLength'])
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2369
		$post_errors[] = 'long_message';
2370
	else
2371
	{
2372
		// Preparse the message.
2373
		$message = $_REQUEST['message'];
2374
		preparsecode($message);
2375
2376
		// Make sure there's still some content left without the tags.
2377 View Code Duplication
		if ($smcFunc['htmltrim'](strip_tags(parse_bbc($smcFunc['htmlspecialchars']($message, ENT_QUOTES), false), '<img>')) === '' && (!allowedTo('admin_forum') || strpos($message, '[html]') === false))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2378
			$post_errors[] = 'no_message';
2379
	}
2380
2381
	// Wrong verification code?
2382
	if (!$user_info['is_admin'] && !isset($_REQUEST['xml']) && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'])
2383
	{
2384
		require_once($sourcedir . '/Subs-Editor.php');
2385
		$verificationOptions = array(
2386
			'id' => 'pm',
2387
		);
2388
		$context['require_verification'] = create_control_verification($verificationOptions, true);
2389
2390
		if (is_array($context['require_verification']))
2391
			$post_errors = array_merge($post_errors, $context['require_verification']);
2392
	}
2393
2394
	// If they did, give a chance to make ammends.
2395
	if (!empty($post_errors) && !$is_recipient_change && !isset($_REQUEST['preview']) && !isset($_REQUEST['xml']))
2396
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
2397
2398
	// Want to take a second glance before you send?
2399
	if (isset($_REQUEST['preview']))
2400
	{
2401
		// Set everything up to be displayed.
2402
		$context['preview_subject'] = $smcFunc['htmlspecialchars']($_REQUEST['subject']);
2403
		$context['preview_message'] = $smcFunc['htmlspecialchars']($_REQUEST['message'], ENT_QUOTES);
2404
		preparsecode($context['preview_message'], true);
2405
2406
		// Parse out the BBC if it is enabled.
2407
		$context['preview_message'] = parse_bbc($context['preview_message']);
2408
2409
		// Censor, as always.
2410
		censorText($context['preview_subject']);
2411
		censorText($context['preview_message']);
2412
2413
		// Set a descriptive title.
2414
		$context['page_title'] = $txt['preview'] . ' - ' . $context['preview_subject'];
2415
2416
		// Pretend they messed up but don't ignore if they really did :P.
2417
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
2418
	}
2419
2420
	// Adding a recipient cause javascript ain't working?
2421
	elseif ($is_recipient_change)
2422
	{
2423
		// Maybe we couldn't find one?
2424
		foreach ($namesNotFound as $recipientType => $names)
2425
		{
2426
			$post_errors[] = 'bad_' . $recipientType;
2427
			foreach ($names as $name)
2428
				$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
2429
		}
2430
2431
		return messagePostError(array(), $namedRecipientList, $recipientList);
2432
	}
2433
2434
	// Want to save this as a draft and think about it some more?
2435
	if ($context['drafts_pm_save'] && isset($_POST['save_draft']))
2436
	{
2437
		SavePMDraft($post_errors, $recipientList);
0 ignored issues
show
Documentation introduced by
$post_errors is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2438
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
0 ignored issues
show
Documentation introduced by
$post_errors is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2439
	}
2440
2441
	// Before we send the PM, let's make sure we don't have an abuse of numbers.
2442
	elseif (!empty($modSettings['max_pm_recipients']) && count($recipientList['to']) + count($recipientList['bcc']) > $modSettings['max_pm_recipients'] && !allowedTo(array('moderate_forum', 'send_mail', 'admin_forum')))
2443
	{
2444
		$context['send_log'] = array(
2445
			'sent' => array(),
2446
			'failed' => array(sprintf($txt['pm_too_many_recipients'], $modSettings['max_pm_recipients'])),
2447
		);
2448
		return messagePostError($post_errors, $namedRecipientList, $recipientList);
2449
	}
2450
2451
	// Protect from message spamming.
2452
	spamProtection('pm');
2453
2454
	// Prevent double submission of this form.
2455
	checkSubmitOnce('check');
2456
2457
	// Do the actual sending of the PM.
2458
	if (!empty($recipientList['to']) || !empty($recipientList['bcc']))
2459
		$context['send_log'] = sendpm($recipientList, $_REQUEST['subject'], $_REQUEST['message'], true, null, !empty($_REQUEST['pm_head']) ? (int) $_REQUEST['pm_head'] : 0);
2460
	else
2461
		$context['send_log'] = array(
2462
			'sent' => array(),
2463
			'failed' => array()
2464
		);
2465
2466
	// Mark the message as "replied to".
2467
	if (!empty($context['send_log']['sent']) && !empty($_REQUEST['replied_to']) && isset($_REQUEST['f']) && $_REQUEST['f'] == 'inbox')
2468
	{
2469
		$smcFunc['db_query']('', '
2470
			UPDATE {db_prefix}pm_recipients
2471
			SET is_read = is_read | 2
2472
			WHERE id_pm = {int:replied_to}
2473
				AND id_member = {int:current_member}',
2474
			array(
2475
				'current_member' => $user_info['id'],
2476
				'replied_to' => (int) $_REQUEST['replied_to'],
2477
			)
2478
		);
2479
	}
2480
2481
	// If one or more of the recipient were invalid, go back to the post screen with the failed usernames.
2482
	if (!empty($context['send_log']['failed']))
2483
		return messagePostError($post_errors, $namesNotFound, array(
2484
			'to' => array_intersect($recipientList['to'], $context['send_log']['failed']),
2485
			'bcc' => array_intersect($recipientList['bcc'], $context['send_log']['failed'])
2486
		));
2487
2488
	// Message sent successfully?
2489
	if (!empty($context['send_log']) && empty($context['send_log']['failed']))
2490
	{
2491
		$context['current_label_redirect'] = $context['current_label_redirect'] . ';done=sent';
2492
2493
		// If we had a PM draft for this one, then its time to remove it since it was just sent
2494
		if ($context['drafts_pm_save'] && !empty($_POST['id_pm_draft']))
2495
			DeleteDraft($_POST['id_pm_draft']);
0 ignored issues
show
Documentation introduced by
$_POST['id_pm_draft'] is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2496
	}
2497
2498
	// Go back to the where they sent from, if possible...
2499
	redirectexit($context['current_label_redirect']);
2500
}
2501
/**
2502
 * This function performs all additional stuff...
2503
 */
2504
function MessageActionsApply()
2505
{
2506
	global $context, $user_info, $options, $smcFunc;
2507
2508
	checkSession('request');
2509
2510
	if (isset($_REQUEST['del_selected']))
2511
		$_REQUEST['pm_action'] = 'delete';
2512
2513
	if (isset($_REQUEST['pm_action']) && $_REQUEST['pm_action'] != '' && !empty($_REQUEST['pms']) && is_array($_REQUEST['pms']))
2514
	{
2515
		foreach ($_REQUEST['pms'] as $pm)
2516
			$_REQUEST['pm_actions'][(int) $pm] = $_REQUEST['pm_action'];
2517
	}
2518
2519
	if (empty($_REQUEST['pm_actions']))
2520
		redirectexit($context['current_label_redirect']);
2521
2522
	// If we are in conversation, we may need to apply this to every message in the conversation.
2523
	if ($context['display_mode'] == 2 && isset($_REQUEST['conversation']))
2524
	{
2525
		$id_pms = array();
2526
		foreach ($_REQUEST['pm_actions'] as $pm => $dummy)
2527
			$id_pms[] = (int) $pm;
2528
2529
		$request = $smcFunc['db_query']('', '
2530
			SELECT id_pm_head, id_pm
2531
			FROM {db_prefix}personal_messages
2532
			WHERE id_pm IN ({array_int:id_pms})',
2533
			array(
2534
				'id_pms' => $id_pms,
2535
			)
2536
		);
2537
		$pm_heads = array();
2538 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2539
			$pm_heads[$row['id_pm_head']] = $row['id_pm'];
2540
		$smcFunc['db_free_result']($request);
2541
2542
		$request = $smcFunc['db_query']('', '
2543
			SELECT id_pm, id_pm_head
2544
			FROM {db_prefix}personal_messages
2545
			WHERE id_pm_head IN ({array_int:pm_heads})',
2546
			array(
2547
				'pm_heads' => array_keys($pm_heads),
2548
			)
2549
		);
2550
		// Copy the action from the single to PM to the others.
2551
		while ($row = $smcFunc['db_fetch_assoc']($request))
2552
		{
2553
			if (isset($pm_heads[$row['id_pm_head']]) && isset($_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]]))
2554
				$_REQUEST['pm_actions'][$row['id_pm']] = $_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]];
2555
		}
2556
		$smcFunc['db_free_result']($request);
2557
	}
2558
2559
	$to_delete = array();
2560
	$to_label = array();
2561
	$label_type = array();
2562
	$labels = array();
2563
	foreach ($_REQUEST['pm_actions'] as $pm => $action)
2564
	{
2565
		if ($action === 'delete')
2566
			$to_delete[] = (int) $pm;
2567
		else
2568
		{
2569
			if (substr($action, 0, 4) == 'add_')
2570
			{
2571
				$type = 'add';
2572
				$action = substr($action, 4);
2573
			}
2574 View Code Duplication
			elseif (substr($action, 0, 4) == 'rem_')
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2575
			{
2576
				$type = 'rem';
2577
				$action = substr($action, 4);
2578
			}
2579
			else
2580
				$type = 'unk';
2581
2582
			if ($action == '-1' || (int) $action > 0)
2583
			{
2584
				$to_label[(int) $pm] = (int) $action;
2585
				$label_type[(int) $pm] = $type;
2586
			}
2587
		}
2588
	}
2589
2590
	// Deleting, it looks like?
2591
	if (!empty($to_delete))
2592
		deleteMessages($to_delete, $context['display_mode'] == 2 ? null : $context['folder']);
2593
2594
	// Are we labeling anything?
2595
	if (!empty($to_label) && $context['folder'] == 'inbox')
2596
	{
2597
		// Are we dealing with conversation view? If so, get all the messages in each conversation
2598
		if ($context['display_mode'] == 2)
2599
		{
2600
			$get_pms = $smcFunc['db_query']('', '
2601
				SELECT pm.id_pm_head, pm.id_pm
2602
				FROM {db_prefix}personal_messages AS pm
2603
					INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
2604
				WHERE pm.id_pm_head IN ({array_int:head_pms})
2605
					AND pm.id_pm NOT IN ({array_int:head_pms})
2606
					AND pmr.id_member = {int:current_member}',
2607
				array(
2608
					'head_pms' => array_keys($to_label),
2609
					'current_member' => $user_info['id'],
2610
				)
2611
			);
2612
2613
			while ($other_pms = $smcFunc['db_query']($get_pms))
2614
			{
2615
				$to_label[$other_pms['id_pm']] = $to_label[$other_pms['id_pm_head']];
2616
			}
2617
2618
			$smcFunc['db_free_result']($get_pms);
2619
		}
2620
2621
		// Get information about each message...
2622
		$request = $smcFunc['db_query']('', '
2623
			SELECT id_pm, in_inbox
2624
			FROM {db_prefix}pm_recipients
2625
			WHERE id_member = {int:current_member}
2626
				AND id_pm IN ({array_int:to_label})
2627
			LIMIT ' . count($to_label),
2628
			array(
2629
				'current_member' => $user_info['id'],
2630
				'to_label' => array_keys($to_label),
2631
			)
2632
		);
2633
2634
		while ($row = $smcFunc['db_fetch_assoc']($request))
2635
		{
2636
			// Get the labels as well, but only if we're not dealing with the inbox
2637
			if ($to_label[$row['id_pm']] != '-1')
2638
			{
2639
				// The JOIN here ensures we only get labels that this user has applied to this PM
2640
				$request2 = $smcFunc['db_query']('', '
2641
					SELECT l.id_label, pml.id_pm
2642
					FROM {db_prefix}pm_labels AS l
2643
						INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2644
					WHERE l.id_member = {int:current_member}
2645
						AND pml.id_pm = {int:current_pm}',
2646
					array(
2647
						'current_member' => $user_info['id'],
2648
						'current_pm' => $row['id_pm'],
2649
					)
2650
				);
2651
2652
				while ($row2 = $smcFunc['db_fetch_assoc']($request2))
2653
				{
2654
					$labels[$row2['id_label']] = $row2['id_label'];
2655
				}
2656
2657
				$smcFunc['db_free_result']($request2);
2658
			}
2659
			elseif ($type == 'rem')
0 ignored issues
show
Bug introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2660
			{
2661
				// If we're removing from the inbox, see if we have at least one other label.
2662
				// This query is faster than the one above
2663
				$request2 = $smcFunc['db_query']('', '
2664
					SELECT COUNT(l.id_label)
2665
					FROM {db_prefix}pm_labels AS l
2666
						INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2667
					WHERE l.id_member = {int:current_member}
2668
						AND pml.id_pm = {int:current_pm}',
2669
					array(
2670
						'current_member' => $user_info['id'],
2671
						'current_pm' => $row['id_pm'],
2672
					)
2673
				);
2674
2675
				// How many labels do you have?
2676
				list ($num_labels) = $smcFunc['db_fetch_assoc']($request2);
2677
2678
				if ($num_labels > 0);
2679
					$context['can_remove_inbox'] = true;
2680
2681
				$smcFunc['db_free_result']($request2);
2682
			}
2683
2684
			// Use this to determine what to do later on...
2685
			$original_labels = $labels;
2686
2687
			// Ignore inbox for now - we'll deal with it later
2688
			if ($to_label[$row['id_pm']] != '-1')
2689
			{
2690
				// If this label is in the list and we're not adding it, remove it
2691
				if (array_key_exists($to_label[$row['id_pm']], $labels) && $type !== 'add')
2692
					unset($labels[$to_label[$row['id_pm']]]);
2693
				else if ($type !== 'rem')
2694
					$labels[$to_label[$row['id_pm']]] = $to_label[$row['id_pm']];
2695
			}
2696
2697
			// Removing all labels or just removing the inbox label
2698
			if ($type == 'rem' && empty($labels))
2699
				$in_inbox = (empty($context['can_remove_inbox']) ? 1 : 0);
2700
			// Adding new labels, but removing inbox and applying new ones
2701
			elseif ($type == 'add' && !empty($options['pm_remove_inbox_label']) && !empty($labels))
2702
				$in_inbox = 0;
2703
			// Just adding it to the inbox
2704
			else
2705
				$in_inbox = 1;
2706
2707
			// Are we adding it to or removing it from the inbox?
2708
			if ($in_inbox != $row['in_inbox'])
2709
			{
2710
				$smcFunc['db_query']('', '
2711
					UPDATE {db_prefix}pm_recipients
2712
					SET in_inbox = {int:in_inbox}
2713
					WHERE id_pm = {int:id_pm}
2714
						AND id_member = {int:current_member}',
2715
					array(
2716
						'current_member' => $user_info['id'],
2717
						'id_pm' => $row['id_pm'],
2718
						'in_inbox' => $in_inbox,
2719
					)
2720
				);
2721
			}
2722
2723
			// Which labels do we not want now?
2724
			$labels_to_remove = array_diff($original_labels, $labels);
2725
2726
			// Don't apply it if it's already applied
2727
			$labels_to_apply = array_diff($labels, $original_labels);
2728
2729
			// Remove labels
2730
			if (!empty($labels_to_remove))
2731
			{
2732
				$smcFunc['db_query']('', '
2733
					DELETE FROM {db_prefix}pm_labeled_messages
2734
					WHERE id_pm = {int:current_pm}
2735
						AND id_label IN ({array_int:labels_to_remove})',
2736
					array(
2737
						'current_pm' => $row['id_pm'],
2738
						'labels_to_remove' => $labels_to_remove,
2739
					)
2740
				);
2741
			}
2742
2743
			// Add new ones
2744 View Code Duplication
			if (!empty($labels_to_apply))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2745
			{
2746
				$inserts = array();
2747
				foreach ($labels_to_apply as $label)
2748
					$inserts[] = array($row['id_pm'], $label);
2749
2750
				$smcFunc['db_insert']('',
2751
					'{db_prefix}pm_labeled_messages',
2752
					array('id_pm' => 'int', 'id_label' => 'int'),
2753
					$inserts,
2754
					array()
2755
				);
2756
			}
2757
		}
2758
		$smcFunc['db_free_result']($request);
2759
	}
2760
2761
	// Back to the folder.
2762
	$_SESSION['pm_selected'] = array_keys($to_label);
2763
	redirectexit($context['current_label_redirect'] . (count($to_label) == 1 ? '#msg' . $_SESSION['pm_selected'][0] : ''), count($to_label) == 1 && isBrowser('ie'));
2764
}
2765
2766
/**
2767
 * Are you sure you want to PERMANENTLY (mostly) delete ALL your messages?
2768
 */
2769
function MessageKillAllQuery()
2770
{
2771
	global $txt, $context;
2772
2773
	// Only have to set up the template....
2774
	$context['sub_template'] = 'ask_delete';
2775
	$context['page_title'] = $txt['delete_all'];
2776
	$context['delete_all'] = $_REQUEST['f'] == 'all';
2777
2778
	// And set the folder name...
2779
	$txt['delete_all'] = str_replace('PMBOX', $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items'], $txt['delete_all']);
2780
}
2781
2782
/**
2783
 * Delete ALL the messages!
2784
 */
2785
function MessageKillAll()
2786
{
2787
	global $context;
2788
2789
	checkSession('get');
2790
2791
	// If all then delete all messages the user has.
2792
	if ($_REQUEST['f'] == 'all')
2793
		deleteMessages(null, null);
2794
	// Otherwise just the selected folder.
2795
	else
2796
		deleteMessages(null, $_REQUEST['f'] != 'sent' ? 'inbox' : 'sent');
2797
2798
	// Done... all gone.
2799
	redirectexit($context['current_label_redirect']);
2800
}
2801
2802
/**
2803
 * This function allows the user to delete all messages older than so many days.
2804
 */
2805
function MessagePrune()
2806
{
2807
	global $txt, $context, $user_info, $scripturl, $smcFunc;
2808
2809
	// Actually delete the messages.
2810
	if (isset($_REQUEST['age']))
2811
	{
2812
		checkSession();
2813
2814
		// Calculate the time to delete before.
2815
		$deleteTime = max(0, time() - (86400 * (int) $_REQUEST['age']));
2816
2817
		// Array to store the IDs in.
2818
		$toDelete = array();
2819
2820
		// Select all the messages they have sent older than $deleteTime.
2821
		$request = $smcFunc['db_query']('', '
2822
			SELECT id_pm
2823
			FROM {db_prefix}personal_messages
2824
			WHERE deleted_by_sender = {int:not_deleted}
2825
				AND id_member_from = {int:current_member}
2826
				AND msgtime < {int:msgtime}',
2827
			array(
2828
				'current_member' => $user_info['id'],
2829
				'not_deleted' => 0,
2830
				'msgtime' => $deleteTime,
2831
			)
2832
		);
2833
		while ($row = $smcFunc['db_fetch_row']($request))
2834
			$toDelete[] = $row[0];
2835
		$smcFunc['db_free_result']($request);
2836
2837
		// Select all messages in their inbox older than $deleteTime.
2838
		$request = $smcFunc['db_query']('', '
2839
			SELECT pmr.id_pm
2840
			FROM {db_prefix}pm_recipients AS pmr
2841
				INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
2842
			WHERE pmr.deleted = {int:not_deleted}
2843
				AND pmr.id_member = {int:current_member}
2844
				AND pm.msgtime < {int:msgtime}',
2845
			array(
2846
				'current_member' => $user_info['id'],
2847
				'not_deleted' => 0,
2848
				'msgtime' => $deleteTime,
2849
			)
2850
		);
2851
		while ($row = $smcFunc['db_fetch_assoc']($request))
2852
			$toDelete[] = $row['id_pm'];
2853
		$smcFunc['db_free_result']($request);
2854
2855
		// Delete the actual messages.
2856
		deleteMessages($toDelete);
2857
2858
		// Go back to their inbox.
2859
		redirectexit($context['current_label_redirect']);
2860
	}
2861
2862
	// Build the link tree elements.
2863
	$context['linktree'][] = array(
2864
		'url' => $scripturl . '?action=pm;sa=prune',
2865
		'name' => $txt['pm_prune']
2866
	);
2867
2868
	$context['sub_template'] = 'prune';
2869
	$context['page_title'] = $txt['pm_prune'];
2870
}
2871
2872
/**
2873
 * Delete the specified personal messages.
2874
 *
2875
 * @param array|null $personal_messages An array containing the IDs of PMs to delete or null to delete all of them
2876
 * @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
2877
 * @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
2878
 */
2879
function deleteMessages($personal_messages, $folder = null, $owner = null)
2880
{
2881
	global $user_info, $smcFunc;
2882
2883
	if ($owner === null)
2884
		$owner = array($user_info['id']);
2885
	elseif (empty($owner))
2886
		return;
2887
	elseif (!is_array($owner))
2888
		$owner = array($owner);
2889
2890
	if ($personal_messages !== null)
2891
	{
2892
		if (empty($personal_messages) || !is_array($personal_messages))
2893
			return;
2894
2895
		foreach ($personal_messages as $index => $delete_id)
2896
			$personal_messages[$index] = (int) $delete_id;
2897
2898
		$where = '
2899
				AND id_pm IN ({array_int:pm_list})';
2900
	}
2901
	else
2902
		$where = '';
2903
2904
	if ($folder == 'sent' || $folder === null)
2905
	{
2906
		$smcFunc['db_query']('', '
2907
			UPDATE {db_prefix}personal_messages
2908
			SET deleted_by_sender = {int:is_deleted}
2909
			WHERE id_member_from IN ({array_int:member_list})
2910
				AND deleted_by_sender = {int:not_deleted}' . $where,
2911
			array(
2912
				'member_list' => $owner,
2913
				'is_deleted' => 1,
2914
				'not_deleted' => 0,
2915
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2916
			)
2917
		);
2918
	}
2919
	if ($folder != 'sent' || $folder === null)
2920
	{
2921
		// Calculate the number of messages each member's gonna lose...
2922
		$request = $smcFunc['db_query']('', '
2923
			SELECT id_member, COUNT(*) AS num_deleted_messages, CASE WHEN is_read & 1 >= 1 THEN 1 ELSE 0 END AS is_read
2924
			FROM {db_prefix}pm_recipients
2925
			WHERE id_member IN ({array_int:member_list})
2926
				AND deleted = {int:not_deleted}' . $where . '
2927
			GROUP BY id_member, is_read',
2928
			array(
2929
				'member_list' => $owner,
2930
				'not_deleted' => 0,
2931
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2932
			)
2933
		);
2934
		// ...And update the statistics accordingly - now including unread messages!.
2935
		while ($row = $smcFunc['db_fetch_assoc']($request))
2936
		{
2937
			if ($row['is_read'])
2938
				updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' . $row['num_deleted_messages']));
2939
			else
2940
				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']));
2941
2942
			// If this is the current member we need to make their message count correct.
2943
			if ($user_info['id'] == $row['id_member'])
2944
			{
2945
				$user_info['messages'] -= $row['num_deleted_messages'];
2946
				if (!($row['is_read']))
2947
					$user_info['unread_messages'] -= $row['num_deleted_messages'];
2948
			}
2949
		}
2950
		$smcFunc['db_free_result']($request);
2951
2952
		// Do the actual deletion.
2953
		$smcFunc['db_query']('', '
2954
			UPDATE {db_prefix}pm_recipients
2955
			SET deleted = {int:is_deleted}
2956
			WHERE id_member IN ({array_int:member_list})
2957
				AND deleted = {int:not_deleted}' . $where,
2958
			array(
2959
				'member_list' => $owner,
2960
				'is_deleted' => 1,
2961
				'not_deleted' => 0,
2962
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2963
			)
2964
		);
2965
2966
		$labels = array();
2967
2968
		// Get any labels that the owner may have applied to this PM
2969
		// The join is here to ensure we only get labels applied by the specified member(s)
2970
		$get_labels = $smcFunc['db_query']('', '
2971
			SELECT pml.id_label
2972
			FROM {db_prefix}pm_labels AS l
2973
			INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
2974
			WHERE l.id_member IN ({array_int:member_list})' . $where,
2975
			array(
2976
				'member_list' => $owner,
2977
				'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2978
			)
2979
		);
2980
2981
		while ($row = $smcFunc['db_fetch_assoc']($get_labels))
2982
		{
2983
			$labels[] = $row['id_label'];
2984
		}
2985
2986
		$smcFunc['db_free_result']($get_labels);
2987
2988
		if (!empty($labels))
2989
		{
2990
			$smcFunc['db_query']('', '
2991
				DELETE FROM {db_prefix}pm_labeled_messages
2992
				WHERE id_label IN ({array_int:labels})' . $where,
2993
				array(
2994
					'labels' => $labels,
2995
					'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
2996
				)
2997
			);
2998
		}
2999
	}
3000
3001
	// If sender and recipients all have deleted their message, it can be removed.
3002
	$request = $smcFunc['db_query']('', '
3003
		SELECT pm.id_pm AS sender, pmr.id_pm
3004
		FROM {db_prefix}personal_messages AS pm
3005
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.deleted = {int:not_deleted})
3006
		WHERE pm.deleted_by_sender = {int:is_deleted} AND pmr.id_pm is null
3007
			' . str_replace('id_pm', 'pm.id_pm', $where),
3008
		array(
3009
			'not_deleted' => 0,
3010
			'is_deleted' => 1,
3011
			'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
3012
		)
3013
	);
3014
	$remove_pms = array();
3015
	while ($row = $smcFunc['db_fetch_assoc']($request))
3016
		$remove_pms[] = $row['sender'];
3017
	$smcFunc['db_free_result']($request);
3018
3019 View Code Duplication
	if (!empty($remove_pms))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3020
	{
3021
		$smcFunc['db_query']('', '
3022
			DELETE FROM {db_prefix}personal_messages
3023
			WHERE id_pm IN ({array_int:pm_list})',
3024
			array(
3025
				'pm_list' => $remove_pms,
3026
			)
3027
		);
3028
3029
		$smcFunc['db_query']('', '
3030
			DELETE FROM {db_prefix}pm_recipients
3031
			WHERE id_pm IN ({array_int:pm_list})',
3032
			array(
3033
				'pm_list' => $remove_pms,
3034
			)
3035
		);
3036
3037
		$smcFunc['db_query']('', '
3038
			DELETE FROM {db_prefix}pm_labeled_messages
3039
			WHERE id_pm IN ({array_int:pm_list})',
3040
			array(
3041
				'pm_list' => $remove_pms,
3042
			)
3043
		);
3044
	}
3045
3046
	// Any cached numbers may be wrong now.
3047
	cache_put_data('labelCounts:' . $user_info['id'], null, 720);
3048
}
3049
3050
/**
3051
 * Mark the specified personal messages read.
3052
 *
3053
 * @param array|null $personal_messages An array of PM IDs to mark or null to mark all
3054
 * @param int|null $label The ID of a label. If set, only messages with this label will be marked.
3055
 * @param int|null $owner If owner is set, marks messages owned by that member id
3056
 */
3057
function markMessages($personal_messages = null, $label = null, $owner = null)
3058
{
3059
	global $user_info, $context, $smcFunc;
3060
3061
	if ($owner === null)
3062
		$owner = $user_info['id'];
3063
3064
	$in_inbox = '';
3065
3066
	// Marking all messages with a specific label as read?
3067
	// If we know which PMs we're marking read, then we don't need label info
3068
	if ($personal_messages === null && $label !== null && $label != '-1')
3069
	{
3070
		$personal_messages = array();
3071
		$get_messages = $smcFunc['db_query']('', '
3072
			SELECT id_pm
3073
			FROM {db_prefix}pm_labeled_messages
3074
			WHERE id_label = {int:current_label}',
3075
			array(
3076
				'current_label' => $label,
3077
			)
3078
		);
3079
3080
		while ($row = $smcFunc['db_fetch_assoc']($get_messages))
3081
		{
3082
			$personal_messages[] = $row['id_pm'];
3083
		}
3084
3085
		$smcFunc['db_free_result']($get_messages);
3086
	}
3087
	elseif ($label = '-1')
3088
	{
3089
		// Marking all PMs in your inbox read
3090
		$in_inbox = '
3091
			AND in_inbox = {int:in_inbox}';
3092
	}
3093
3094
	$smcFunc['db_query']('', '
3095
		UPDATE {db_prefix}pm_recipients
3096
		SET is_read = is_read | 1
3097
		WHERE id_member = {int:id_member}
3098
			AND NOT (is_read & 1 >= 1)' . ($personal_messages !== null ? '
3099
			AND id_pm IN ({array_int:personal_messages})' : '') . $in_inbox,
3100
		array(
3101
			'personal_messages' => $personal_messages,
3102
			'id_member' => $owner,
3103
			'in_inbox' => 1,
3104
		)
3105
	);
3106
3107
	// If something wasn't marked as read, get the number of unread messages remaining.
3108
	if ($smcFunc['db_affected_rows']() > 0)
3109
	{
3110
		if ($owner == $user_info['id'])
3111
		{
3112
			foreach ($context['labels'] as $label)
3113
				$context['labels'][(int) $label['id']]['unread_messages'] = 0;
3114
		}
3115
3116
		$result = $smcFunc['db_query']('', '
3117
			SELECT id_pm, in_inbox, COUNT(*) AS num
3118
			FROM {db_prefix}pm_recipients
3119
			WHERE id_member = {int:id_member}
3120
				AND NOT (is_read & 1 >= 1)
3121
				AND deleted = {int:is_not_deleted}
3122
			GROUP BY id_pm, in_inbox',
3123
			array(
3124
				'id_member' => $owner,
3125
				'is_not_deleted' => 0,
3126
			)
3127
		);
3128
		$total_unread = 0;
3129
		while ($row = $smcFunc['db_fetch_assoc']($result))
3130
		{
3131
			$total_unread += $row['num'];
3132
3133
			if ($owner != $user_info['id'] || empty($row['id_pm']))
3134
				continue;
3135
3136
			$this_labels = array();
3137
3138
			// Get all the labels
3139
			$result2 = $smcFunc['db_query']('', '
3140
				SELECT pml.id_label
3141
				FROM {db_prefix}pm_labels AS l
3142
					INNER JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_label = l.id_label)
3143
				WHERE l.id_member = {int:id_member}
3144
					AND pml.id_pm = {int:current_pm}',
3145
				array(
3146
					'id_member' => $owner,
3147
					'current_pm' => $row['id_pm'],
3148
				)
3149
			);
3150
3151
			while ($row2 = $smcFunc['db_fetch_assoc']($result2))
3152
			{
3153
				$this_labels[] = $row2['id_label'];
3154
			}
3155
3156
			$smcFunc['db_free_result']($result2);
3157
3158
			foreach ($this_labels as $this_label)
3159
				$context['labels'][$this_label]['unread_messages'] += $row['num'];
3160
3161
			if ($row['in_inbox'] == 1)
3162
				$context['labels'][-1]['unread_messages'] += $row['num'];
3163
		}
3164
		$smcFunc['db_free_result']($result);
3165
3166
		// Need to store all this.
3167
		cache_put_data('labelCounts:' . $owner, $context['labels'], 720);
3168
		updateMemberData($owner, array('unread_messages' => $total_unread));
3169
3170
		// If it was for the current member, reflect this in the $user_info array too.
3171
		if ($owner == $user_info['id'])
3172
			$user_info['unread_messages'] = $total_unread;
3173
	}
3174
}
3175
3176
/**
3177
 * This function handles adding, deleting and editing labels on messages.
3178
 */
3179
function ManageLabels()
3180
{
3181
	global $txt, $context, $user_info, $scripturl, $smcFunc;
3182
3183
	// Build the link tree elements...
3184
	$context['linktree'][] = array(
3185
		'url' => $scripturl . '?action=pm;sa=manlabels',
3186
		'name' => $txt['pm_manage_labels']
3187
	);
3188
3189
	$context['page_title'] = $txt['pm_manage_labels'];
3190
	$context['sub_template'] = 'labels';
3191
3192
	$the_labels = array();
3193
	$labels_to_add = array();
3194
	$labels_to_remove = array();
3195
	$label_updates = array();
3196
3197
	// Add all existing labels to the array to save, slashing them as necessary...
3198
	foreach ($context['labels'] as $label)
3199
	{
3200
		if ($label['id'] != -1)
3201
			$the_labels[$label['id']] = $label['name'];
3202
	}
3203
3204
	if (isset($_POST[$context['session_var']]))
3205
	{
3206
		checkSession();
3207
3208
		// This will be for updating messages.
3209
		$message_changes = array();
3210
		$rule_changes = array();
3211
3212
		// Will most likely need this.
3213
		LoadRules();
3214
3215
		// Adding a new label?
3216
		if (isset($_POST['add']))
3217
		{
3218
			$_POST['label'] = strtr($smcFunc['htmlspecialchars'](trim($_POST['label'])), array(',' => '&#044;'));
3219
3220 View Code Duplication
			if ($smcFunc['strlen']($_POST['label']) > 30)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3221
				$_POST['label'] = $smcFunc['substr']($_POST['label'], 0, 30);
3222
			if ($_POST['label'] != '')
3223
			{
3224
				$the_labels[] = $_POST['label'];
3225
				$labels_to_add[] = $_POST['label'];
3226
			}
3227
		}
3228
		// Deleting an existing label?
3229
		elseif (isset($_POST['delete'], $_POST['delete_label']))
3230
		{
3231
			foreach ($_POST['delete_label'] AS $label => $dummy)
3232
			{
3233
				unset($the_labels[$label]);
3234
				$labels_to_remove[] = $label;
3235
			}
3236
		}
3237
		// The hardest one to deal with... changes.
3238
		elseif (isset($_POST['save']) && !empty($_POST['label_name']))
3239
		{
3240
			foreach ($the_labels as $id => $name)
3241
			{
3242
				if ($id == -1)
3243
					continue;
3244
				elseif (isset($_POST['label_name'][$id]))
3245
				{
3246
					$_POST['label_name'][$id] = trim(strtr($smcFunc['htmlspecialchars']($_POST['label_name'][$id]), array(',' => '&#044;')));
3247
3248
					if ($smcFunc['strlen']($_POST['label_name'][$id]) > 30)
3249
						$_POST['label_name'][$id] = $smcFunc['substr']($_POST['label_name'][$id], 0, 30);
3250
					if ($_POST['label_name'][$id] != '')
3251
					{
3252
						// Changing the name of this label?
3253
						if ($the_labels[$id] != $_POST['label_name'][$id])
3254
							$label_updates[$id] = $_POST['label_name'][$id];
3255
3256
						$the_labels[(int) $id] = $_POST['label_name'][$id];
3257
3258
					}
3259
					else
3260
					{
3261
						unset($the_labels[(int) $id]);
3262
						$labels_to_remove[] = $id;
3263
						$message_changes[(int) $id] = true;
3264
					}
3265
				}
3266
			}
3267
		}
3268
3269
		// Save any new labels
3270 View Code Duplication
		if (!empty($labels_to_add))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3271
		{
3272
			$inserts = array();
3273
			foreach ($labels_to_add AS $label)
3274
				$inserts[] = array($user_info['id'], $label);
3275
3276
			$smcFunc['db_insert']('', '{db_prefix}pm_labels', array('id_member' => 'int', 'name' => 'string-30'), $inserts, array());
3277
		}
3278
3279
		// Update existing labels as needed
3280
		if (!empty($label_upates))
0 ignored issues
show
Bug introduced by
The variable $label_upates does not exist. Did you mean $label_updates?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
3281
		{
3282
			foreach ($label_updates AS $id => $name)
3283
			{
3284
				$smcFunc['db_query']('', '
3285
					UPDATE {db_prefix}labels
3286
					SET name = {string:name}
3287
					WHERE id_label = {int:id_label}',
3288
					array(
3289
						'name' => $name,
3290
						'id' => $id
3291
					)
3292
				);
3293
			}
3294
		}
3295
3296
		// Now the fun part... Deleting labels.
3297
		if (!empty($labels_to_remove))
3298
		{
3299
			// First delete the labels
3300
			$smcFunc['db_query']('', '
3301
				DELETE FROM {db_prefix}pm_labels
3302
				WHERE id_label IN ({array_int:labels_to_delete})',
3303
				array(
3304
					'labels_to_delete' => $labels_to_remove,
3305
				)
3306
			);
3307
3308
			// Now remove the now-deleted labels from any PMs...
3309
			$smcFunc['db_query']('', '
3310
				DELETE FROM {db_prefix}pm_labeled_messages
3311
				WHERE id_label IN ({array_int:labels_to_delete})',
3312
				array(
3313
					'labels_to_delete' => $labels_to_remove,
3314
				)
3315
			);
3316
3317
			// Get any PMs with no labels which aren't in the inbox
3318
			$get_stranded_pms = $smcFunc['db_query']('', '
3319
				SELECT pmr.id_pm
3320
				FROM {db_prefix}pm_recipients AS pmr
3321
					LEFT JOIN {db_prefix}pm_labeled_messages AS pml ON (pml.id_pm = pmr.id_pm)
3322
				WHERE pml.id_label IS NULL
3323
					AND pmr.in_inbox = {int:not_in_inbox}
3324
					AND pmr.deleted = {int:not_deleted}
3325
					AND pmr.id_member = {int:current_member}',
3326
				array(
3327
					'not_in_inbox' => 0,
3328
					'not_deleted' => 0,
3329
					'current_member' => $user_info['id'],
3330
				)
3331
			);
3332
3333
			$stranded_messages = array();
3334
			while ($row = $smcFunc['db_fetch_assoc']($get_stranded_pms))
3335
			{
3336
				$stranded_messages[] = $row['id_pm'];
3337
			}
3338
3339
			$smcFunc['db_free_result']($get_stranded_pms);
3340
3341
			// Move these back to the inbox if necessary
3342
			if (!empty($stranded_messages))
3343
			{
3344
				// We now have more messages in the inbox
3345
				$context['labels'][-1]['messages'] += count($stranded_messages);
3346
				$smcFunc['db_query']('', '
3347
					UPDATE {db_prefix}pm_recipients
3348
					SET in_inbox = {int:in_inbox}
3349
					WHERE id_pm IN ({array_int:stranded_messages})
3350
						AND id_member = {int:current_member}',
3351
					array(
3352
						'stranded_messages' => $stranded_messages,
3353
						'in_inbox' => 1,
3354
					)
3355
				);
3356
			}
3357
3358
			// Now do the same the rules - check through each rule.
3359
			foreach ($context['rules'] as $k => $rule)
3360
			{
3361
				// Each action...
3362
				foreach ($rule['actions'] as $k2 => $action)
3363
				{
3364
					if ($action['t'] != 'lab' || !in_array($action['v'], $labels_to_remove))
3365
						continue;
3366
3367
					$rule_changes[] = $rule['id'];
3368
3369
					// Can't apply this label anymore if it doesn't exist
3370
					unset($context['rules'][$k]['actions'][$k2]);
3371
				}
3372
			}
3373
		}
3374
3375
		// If we have rules to change do so now.
3376
		if (!empty($rule_changes))
3377
		{
3378
			$rule_changes = array_unique($rule_changes);
3379
			// Update/delete as appropriate.
3380
			foreach ($rule_changes as $k => $id)
3381
				if (!empty($context['rules'][$id]['actions']))
3382
				{
3383
					$smcFunc['db_query']('', '
3384
						UPDATE {db_prefix}pm_rules
3385
						SET actions = {string:actions}
3386
						WHERE id_rule = {int:id_rule}
3387
							AND id_member = {int:current_member}',
3388
						array(
3389
							'current_member' => $user_info['id'],
3390
							'id_rule' => $id,
3391
							'actions' => json_encode($context['rules'][$id]['actions']),
3392
						)
3393
					);
3394
					unset($rule_changes[$k]);
3395
				}
3396
3397
			// Anything left here means it's lost all actions...
3398
			if (!empty($rule_changes))
3399
				$smcFunc['db_query']('', '
3400
					DELETE FROM {db_prefix}pm_rules
3401
					WHERE id_rule IN ({array_int:rule_list})
3402
							AND id_member = {int:current_member}',
3403
					array(
3404
						'current_member' => $user_info['id'],
3405
						'rule_list' => $rule_changes,
3406
					)
3407
				);
3408
		}
3409
3410
		// Make sure we're not caching this!
3411
		cache_put_data('labelCounts:' . $user_info['id'], null, 720);
3412
3413
		// To make the changes appear right away, redirect.
3414
		redirectexit('action=pm;sa=manlabels');
3415
	}
3416
}
3417
3418
/**
3419
 * Allows to edit Personal Message Settings.
3420
 *
3421
 * @uses Profile.php
3422
 * @uses Profile-Modify.php
3423
 * @uses Profile template.
3424
 * @uses Profile language file.
3425
 */
3426
function MessageSettings()
3427
{
3428
	global $txt, $user_info, $context, $sourcedir;
3429
	global $scripturl, $profile_vars, $cur_profile, $user_profile;
3430
3431
	// Need this for the display.
3432
	require_once($sourcedir . '/Profile.php');
3433
	require_once($sourcedir . '/Profile-Modify.php');
3434
3435
	// We want them to submit back to here.
3436
	$context['profile_custom_submit_url'] = $scripturl . '?action=pm;sa=settings;save';
3437
3438
	loadMemberData($user_info['id'], false, 'profile');
3439
	$cur_profile = $user_profile[$user_info['id']];
3440
3441
	loadLanguage('Profile');
3442
	loadTemplate('Profile');
3443
3444
	// Since this is internally handled with the profile code because that's how it was done ages ago
3445
	// we have to set everything up for handling this...
3446
	$context['page_title'] = $txt['pm_settings'];
3447
	$context['user']['is_owner'] = true;
3448
	$context['id_member'] = $user_info['id'];
3449
	$context['require_password'] = false;
3450
	$context['menu_item_selected'] = 'settings';
3451
	$context['submit_button_text'] = $txt['pm_settings'];
3452
	$context['profile_header_text'] = $txt['personal_messages'];
3453
	$context['sub_template'] = 'edit_options';
3454
	$context['page_desc'] = $txt['pm_settings_desc'];
3455
3456
	loadThemeOptions($user_info['id']);
3457
	loadCustomFields($user_info['id'], 'pmprefs');
3458
3459
	// Add our position to the linktree.
3460
	$context['linktree'][] = array(
3461
		'url' => $scripturl . '?action=pm;sa=settings',
3462
		'name' => $txt['pm_settings']
3463
	);
3464
3465
	// Are they saving?
3466
	if (isset($_REQUEST['save']))
3467
	{
3468
		checkSession();
3469
3470
		// Mimic what profile would do.
3471
		$_POST = htmltrim__recursive($_POST);
3472
		$_POST = htmlspecialchars__recursive($_POST);
3473
3474
		// Save the fields.
3475
		saveProfileFields();
3476
3477
		if (!empty($profile_vars))
3478
			updateMemberData($user_info['id'], $profile_vars);
3479
	}
3480
3481
	setupProfileContext(
3482
		array(
3483
			'pm_prefs',
3484
		)
3485
	);
3486
}
3487
3488
/**
3489
 * Allows the user to report a personal message to an administrator.
3490
 *
3491
 * - In the first instance requires that the ID of the message to report is passed through $_GET.
3492
 * - It allows the user to report to either a particular administrator - or the whole admin team.
3493
 * - It will forward on a copy of the original message without allowing the reporter to make changes.
3494
 *
3495
 * @uses report_message sub-template.
3496
 */
3497
function ReportMessage()
3498
{
3499
	global $txt, $context, $scripturl;
3500
	global $user_info, $language, $modSettings, $smcFunc;
3501
3502
	// Check that this feature is even enabled!
3503
	if (empty($modSettings['enableReportPM']) || empty($_REQUEST['pmsg']))
3504
		fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3505
3506
	$pmsg = (int) $_REQUEST['pmsg'];
3507
3508
	if (!isAccessiblePM($pmsg, 'inbox'))
3509
		fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3510
3511
	$context['pm_id'] = $pmsg;
3512
	$context['page_title'] = $txt['pm_report_title'];
3513
3514
	// If we're here, just send the user to the template, with a few useful context bits.
3515
	if (!isset($_POST['report']))
3516
	{
3517
		$context['sub_template'] = 'report_message';
3518
3519
		// @todo I don't like being able to pick who to send it to.  Favoritism, etc. sucks.
3520
		// Now, get all the administrators.
3521
		$request = $smcFunc['db_query']('', '
3522
			SELECT id_member, real_name
3523
			FROM {db_prefix}members
3524
			WHERE id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0
3525
			ORDER BY real_name',
3526
			array(
3527
				'admin_group' => 1,
3528
			)
3529
		);
3530
		$context['admins'] = array();
3531
		while ($row = $smcFunc['db_fetch_assoc']($request))
3532
			$context['admins'][$row['id_member']] = $row['real_name'];
3533
		$smcFunc['db_free_result']($request);
3534
3535
		// How many admins in total?
3536
		$context['admin_count'] = count($context['admins']);
3537
	}
3538
	// Otherwise, let's get down to the sending stuff.
3539
	else
3540
	{
3541
		// Check the session before proceeding any further!
3542
		checkSession();
3543
3544
		// First, pull out the message contents, and verify it actually went to them!
3545
		$request = $smcFunc['db_query']('', '
3546
			SELECT pm.subject, pm.body, pm.msgtime, pm.id_member_from, COALESCE(m.real_name, pm.from_name) AS sender_name
3547
			FROM {db_prefix}personal_messages AS pm
3548
				INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
3549
				LEFT JOIN {db_prefix}members AS m ON (m.id_member = pm.id_member_from)
3550
			WHERE pm.id_pm = {int:id_pm}
3551
				AND pmr.id_member = {int:current_member}
3552
				AND pmr.deleted = {int:not_deleted}
3553
			LIMIT 1',
3554
			array(
3555
				'current_member' => $user_info['id'],
3556
				'id_pm' => $context['pm_id'],
3557
				'not_deleted' => 0,
3558
			)
3559
		);
3560
		// Can only be a hacker here!
3561
		if ($smcFunc['db_num_rows']($request) == 0)
3562
			fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3563
		list ($subject, $body, $time, $memberFromID, $memberFromName) = $smcFunc['db_fetch_row']($request);
3564
		$smcFunc['db_free_result']($request);
3565
3566
		// Remove the line breaks...
3567
		$body = preg_replace('~<br ?/?' . '>~i', "\n", $body);
3568
3569
		// Get any other recipients of the email.
3570
		$request = $smcFunc['db_query']('', '
3571
			SELECT mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, pmr.bcc
3572
			FROM {db_prefix}pm_recipients AS pmr
3573
				LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
3574
			WHERE pmr.id_pm = {int:id_pm}
3575
				AND pmr.id_member != {int:current_member}',
3576
			array(
3577
				'current_member' => $user_info['id'],
3578
				'id_pm' => $context['pm_id'],
3579
			)
3580
		);
3581
		$recipients = array();
3582
		$hidden_recipients = 0;
3583
		while ($row = $smcFunc['db_fetch_assoc']($request))
3584
		{
3585
			// If it's hidden still don't reveal their names - privacy after all ;)
3586
			if ($row['bcc'])
3587
				$hidden_recipients++;
3588
			else
3589
				$recipients[] = '[url=' . $scripturl . '?action=profile;u=' . $row['id_member_to'] . ']' . $row['to_name'] . '[/url]';
3590
		}
3591
		$smcFunc['db_free_result']($request);
3592
3593
		if ($hidden_recipients)
3594
			$recipients[] = sprintf($txt['pm_report_pm_hidden'], $hidden_recipients);
3595
3596
		// Now let's get out and loop through the admins.
3597
		$request = $smcFunc['db_query']('', '
3598
			SELECT id_member, real_name, lngfile
3599
			FROM {db_prefix}members
3600
			WHERE (id_group = {int:admin_id} OR FIND_IN_SET({int:admin_id}, additional_groups) != 0)
3601
				' . (empty($_POST['id_admin']) ? '' : 'AND id_member = {int:specific_admin}') . '
3602
			ORDER BY lngfile',
3603
			array(
3604
				'admin_id' => 1,
3605
				'specific_admin' => isset($_POST['id_admin']) ? (int) $_POST['id_admin'] : 0,
3606
			)
3607
		);
3608
3609
		// Maybe we shouldn't advertise this?
3610
		if ($smcFunc['db_num_rows']($request) == 0)
3611
			fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3612
3613
		$memberFromName = un_htmlspecialchars($memberFromName);
3614
3615
		// Prepare the message storage array.
3616
		$messagesToSend = array();
3617
		// Loop through each admin, and add them to the right language pile...
3618
		while ($row = $smcFunc['db_fetch_assoc']($request))
3619
		{
3620
			// Need to send in the correct language!
3621
			$cur_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'];
3622
3623
			if (!isset($messagesToSend[$cur_language]))
3624
			{
3625
				loadLanguage('PersonalMessage', $cur_language, false);
3626
3627
				// Make the body.
3628
				$report_body = str_replace(array('{REPORTER}', '{SENDER}'), array(un_htmlspecialchars($user_info['name']), $memberFromName), $txt['pm_report_pm_user_sent']);
3629
				$report_body .= "\n" . '[b]' . $_POST['reason'] . '[/b]' . "\n\n";
3630
				if (!empty($recipients))
3631
					$report_body .= $txt['pm_report_pm_other_recipients'] . ' ' . implode(', ', $recipients) . "\n\n";
3632
				$report_body .= $txt['pm_report_pm_unedited_below'] . "\n" . '[quote author=' . (empty($memberFromID) ? '&quot;' . $memberFromName . '&quot;' : $memberFromName . ' link=action=profile;u=' . $memberFromID . ' date=' . $time) . ']' . "\n" . un_htmlspecialchars($body) . '[/quote]';
3633
3634
				// Plonk it in the array ;)
3635
				$messagesToSend[$cur_language] = array(
3636
					'subject' => ($smcFunc['strpos']($subject, $txt['pm_report_pm_subject']) === false ? $txt['pm_report_pm_subject'] : '') . un_htmlspecialchars($subject),
3637
					'body' => $report_body,
3638
					'recipients' => array(
3639
						'to' => array(),
3640
						'bcc' => array()
3641
					),
3642
				);
3643
			}
3644
3645
			// Add them to the list.
3646
			$messagesToSend[$cur_language]['recipients']['to'][$row['id_member']] = $row['id_member'];
3647
		}
3648
		$smcFunc['db_free_result']($request);
3649
3650
		// Send a different email for each language.
3651
		foreach ($messagesToSend as $lang => $message)
3652
			sendpm($message['recipients'], $message['subject'], $message['body']);
3653
3654
		// Give the user their own language back!
3655
		if (!empty($modSettings['userLanguage']))
3656
			loadLanguage('PersonalMessage', '', false);
3657
3658
		// Leave them with a template.
3659
		$context['sub_template'] = 'report_message_complete';
3660
	}
3661
}
3662
3663
/**
3664
 * List all rules, and allow adding/entering etc...
3665
 */
3666
function ManageRules()
3667
{
3668
	global $txt, $context, $user_info, $scripturl, $smcFunc;
3669
3670
	// The link tree - gotta have this :o
3671
	$context['linktree'][] = array(
3672
		'url' => $scripturl . '?action=pm;sa=manrules',
3673
		'name' => $txt['pm_manage_rules']
3674
	);
3675
3676
	$context['page_title'] = $txt['pm_manage_rules'];
3677
	$context['sub_template'] = 'rules';
3678
3679
	// Load them... load them!!
3680
	LoadRules();
3681
3682
	// Likely to need all the groups!
3683
	$request = $smcFunc['db_query']('', '
3684
		SELECT mg.id_group, mg.group_name, COALESCE(gm.id_member, 0) AS can_moderate, mg.hidden
3685
		FROM {db_prefix}membergroups AS mg
3686
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
3687
		WHERE mg.min_posts = {int:min_posts}
3688
			AND mg.id_group != {int:moderator_group}
3689
			AND mg.hidden = {int:not_hidden}
3690
		ORDER BY mg.group_name',
3691
		array(
3692
			'current_member' => $user_info['id'],
3693
			'min_posts' => -1,
3694
			'moderator_group' => 3,
3695
			'not_hidden' => 0,
3696
		)
3697
	);
3698
	$context['groups'] = array();
3699
	while ($row = $smcFunc['db_fetch_assoc']($request))
3700
	{
3701
		// Hide hidden groups!
3702
		if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups'))
3703
			continue;
3704
3705
		$context['groups'][$row['id_group']] = $row['group_name'];
3706
	}
3707
	$smcFunc['db_free_result']($request);
3708
3709
	// Applying all rules?
3710
	if (isset($_GET['apply']))
3711
	{
3712
		checkSession('get');
3713
3714
		ApplyRules(true);
3715
		redirectexit('action=pm;sa=manrules');
3716
	}
3717
	// Editing a specific one?
3718
	if (isset($_GET['add']))
3719
	{
3720
		$context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']]) ? (int) $_GET['rid'] : 0;
3721
		$context['sub_template'] = 'add_rule';
3722
3723
		// Current rule information...
3724
		if ($context['rid'])
3725
		{
3726
			$context['rule'] = $context['rules'][$context['rid']];
3727
			$members = array();
3728
			// Need to get member names!
3729
			foreach ($context['rule']['criteria'] as $k => $criteria)
3730
				if ($criteria['t'] == 'mid' && !empty($criteria['v']))
3731
					$members[(int) $criteria['v']] = $k;
3732
3733 View Code Duplication
			if (!empty($members))
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3734
			{
3735
				$request = $smcFunc['db_query']('', '
3736
					SELECT id_member, member_name
3737
					FROM {db_prefix}members
3738
					WHERE id_member IN ({array_int:member_list})',
3739
					array(
3740
						'member_list' => array_keys($members),
3741
					)
3742
				);
3743
				while ($row = $smcFunc['db_fetch_assoc']($request))
3744
					$context['rule']['criteria'][$members[$row['id_member']]]['v'] = $row['member_name'];
3745
				$smcFunc['db_free_result']($request);
3746
			}
3747
		}
3748
		else
3749
			$context['rule'] = array(
3750
				'id' => '',
3751
				'name' => '',
3752
				'criteria' => array(),
3753
				'actions' => array(),
3754
				'logic' => 'and',
3755
			);
3756
	}
3757
	// Saving?
3758
	elseif (isset($_GET['save']))
3759
	{
3760
		checkSession();
3761
		$context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']]) ? (int) $_GET['rid'] : 0;
3762
3763
		// Name is easy!
3764
		$ruleName = $smcFunc['htmlspecialchars'](trim($_POST['rule_name']));
3765
		if (empty($ruleName))
3766
			fatal_lang_error('pm_rule_no_name', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3767
3768
		// Sanity check...
3769
		if (empty($_POST['ruletype']) || empty($_POST['acttype']))
3770
			fatal_lang_error('pm_rule_no_criteria', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3771
3772
		// Let's do the criteria first - it's also hardest!
3773
		$criteria = array();
3774
		foreach ($_POST['ruletype'] as $ind => $type)
3775
		{
3776
			// Check everything is here...
3777
			if ($type == 'gid' && (!isset($_POST['ruledefgroup'][$ind]) || !isset($context['groups'][$_POST['ruledefgroup'][$ind]])))
3778
				continue;
3779
			elseif ($type != 'bud' && !isset($_POST['ruledef'][$ind]))
3780
				continue;
3781
3782
			// Members need to be found.
3783
			if ($type == 'mid')
3784
			{
3785
				$name = trim($_POST['ruledef'][$ind]);
3786
				$request = $smcFunc['db_query']('', '
3787
					SELECT id_member
3788
					FROM {db_prefix}members
3789
					WHERE real_name = {string:member_name}
3790
						OR member_name = {string:member_name}',
3791
					array(
3792
						'member_name' => $name,
3793
					)
3794
				);
3795
				if ($smcFunc['db_num_rows']($request) == 0)
3796
				{
3797
					loadLanguage('Errors');
3798
					fatal_lang_error('invalid_username', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3799
				}
3800
				list ($memID) = $smcFunc['db_fetch_row']($request);
3801
				$smcFunc['db_free_result']($request);
3802
3803
				$criteria[] = array('t' => 'mid', 'v' => $memID);
3804
			}
3805
			elseif ($type == 'bud')
3806
				$criteria[] = array('t' => 'bud', 'v' => 1);
3807
			elseif ($type == 'gid')
3808
				$criteria[] = array('t' => 'gid', 'v' => (int) $_POST['ruledefgroup'][$ind]);
3809
			elseif (in_array($type, array('sub', 'msg')) && trim($_POST['ruledef'][$ind]) != '')
3810
				$criteria[] = array('t' => $type, 'v' => $smcFunc['htmlspecialchars'](trim($_POST['ruledef'][$ind])));
3811
		}
3812
3813
		// Also do the actions!
3814
		$actions = array();
3815
		$doDelete = 0;
3816
		$isOr = $_POST['rule_logic'] == 'or' ? 1 : 0;
3817
		foreach ($_POST['acttype'] as $ind => $type)
3818
		{
3819
			// Picking a valid label?
3820
			if ($type == 'lab' && (!isset($_POST['labdef'][$ind]) || !isset($context['labels'][$_POST['labdef'][$ind]])))
3821
				continue;
3822
3823
			// Record what we're doing.
3824
			if ($type == 'del')
3825
				$doDelete = 1;
3826
			elseif ($type == 'lab')
3827
				$actions[] = array('t' => 'lab', 'v' => (int) $_POST['labdef'][$ind]);
3828
		}
3829
3830
		if (empty($criteria) || (empty($actions) && !$doDelete))
3831
			fatal_lang_error('pm_rule_no_criteria', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3832
3833
		// What are we storing?
3834
		$criteria = json_encode($criteria);
3835
		$actions = json_encode($actions);
3836
3837
		// Create the rule?
3838
		if (empty($context['rid']))
3839
			$smcFunc['db_insert']('',
3840
				'{db_prefix}pm_rules',
3841
				array(
3842
					'id_member' => 'int', 'rule_name' => 'string', 'criteria' => 'string', 'actions' => 'string',
3843
					'delete_pm' => 'int', 'is_or' => 'int',
3844
				),
3845
				array(
3846
					$user_info['id'], $ruleName, $criteria, $actions, $doDelete, $isOr,
3847
				),
3848
				array('id_rule')
3849
			);
3850
		else
3851
			$smcFunc['db_query']('', '
3852
				UPDATE {db_prefix}pm_rules
3853
				SET rule_name = {string:rule_name}, criteria = {string:criteria}, actions = {string:actions},
3854
					delete_pm = {int:delete_pm}, is_or = {int:is_or}
3855
				WHERE id_rule = {int:id_rule}
3856
					AND id_member = {int:current_member}',
3857
				array(
3858
					'current_member' => $user_info['id'],
3859
					'delete_pm' => $doDelete,
3860
					'is_or' => $isOr,
3861
					'id_rule' => $context['rid'],
3862
					'rule_name' => $ruleName,
3863
					'criteria' => $criteria,
3864
					'actions' => $actions,
3865
				)
3866
			);
3867
3868
		redirectexit('action=pm;sa=manrules');
3869
	}
3870
	// Deleting?
3871
	elseif (isset($_POST['delselected']) && !empty($_POST['delrule']))
3872
	{
3873
		checkSession();
3874
		$toDelete = array();
3875
		foreach ($_POST['delrule'] as $k => $v)
3876
			$toDelete[] = (int) $k;
3877
3878
		if (!empty($toDelete))
3879
			$smcFunc['db_query']('', '
3880
				DELETE FROM {db_prefix}pm_rules
3881
				WHERE id_rule IN ({array_int:delete_list})
3882
					AND id_member = {int:current_member}',
3883
				array(
3884
					'current_member' => $user_info['id'],
3885
					'delete_list' => $toDelete,
3886
				)
3887
			);
3888
3889
		redirectexit('action=pm;sa=manrules');
3890
	}
3891
}
3892
3893
/**
3894
 * This will apply rules to all unread messages. If all_messages is set will, clearly, do it to all!
3895
 *
3896
 * @param bool $all_messages Whether to apply this to all messages or just unread ones
3897
 */
3898
function ApplyRules($all_messages = false)
3899
{
3900
	global $user_info, $smcFunc, $context, $options;
3901
3902
	// Want this - duh!
3903
	loadRules();
3904
3905
	// No rules?
3906
	if (empty($context['rules']))
3907
		return;
3908
3909
	// Just unread ones?
3910
	$ruleQuery = $all_messages ? '' : ' AND pmr.is_new = 1';
3911
3912
	// @todo Apply all should have timeout protection!
3913
	// Get all the messages that match this.
3914
	$request = $smcFunc['db_query']('', '
3915
		SELECT
3916
			pmr.id_pm, pm.id_member_from, pm.subject, pm.body, mem.id_group
3917
		FROM {db_prefix}pm_recipients AS pmr
3918
			INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
3919
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
3920
		WHERE pmr.id_member = {int:current_member}
3921
			AND pmr.deleted = {int:not_deleted}
3922
			' . $ruleQuery,
3923
		array(
3924
			'current_member' => $user_info['id'],
3925
			'not_deleted' => 0,
3926
		)
3927
	);
3928
	$actions = array();
3929
	while ($row = $smcFunc['db_fetch_assoc']($request))
3930
	{
3931
		foreach ($context['rules'] as $rule)
3932
		{
3933
			$match = false;
3934
			// Loop through all the criteria hoping to make a match.
3935
			foreach ($rule['criteria'] as $criterium)
3936
			{
3937
				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))
3938
					$match = true;
3939
				// If we're adding and one criteria don't match then we stop!
3940
				elseif ($rule['logic'] == 'and')
3941
				{
3942
					$match = false;
3943
					break;
3944
				}
3945
			}
3946
3947
			// If we have a match the rule must be true - act!
3948
			if ($match)
3949
			{
3950
				if ($rule['delete'])
3951
					$actions['deletes'][] = $row['id_pm'];
3952
				else
3953
				{
3954
					foreach ($rule['actions'] as $ruleAction)
3955
					{
3956
						if ($ruleAction['t'] == 'lab')
3957
						{
3958
							// Get a basic pot started!
3959
							if (!isset($actions['labels'][$row['id_pm']]))
3960
								$actions['labels'][$row['id_pm']] = array();
3961
							$actions['labels'][$row['id_pm']][] = $ruleAction['v'];
3962
						}
3963
					}
3964
				}
3965
			}
3966
		}
3967
	}
3968
	$smcFunc['db_free_result']($request);
3969
3970
	// Deletes are easy!
3971
	if (!empty($actions['deletes']))
3972
		deleteMessages($actions['deletes']);
3973
3974
	// Relabel?
3975
	if (!empty($actions['labels']))
3976
	{
3977
		foreach ($actions['labels'] as $pm => $labels)
3978
		{
3979
			// Quickly check each label is valid!
3980
			$realLabels = array();
3981
			foreach ($context['labels'] as $label)
3982
			{
3983
				if (in_array($label['id'], $labels) && $label['id'] != -1 || empty($options['pm_remove_inbox_label']))
3984
				{
3985
					// Make sure this stays in the inbox
3986
					if ($label['id'] == '-1')
3987
					{
3988
						$smcFunc['db_query']('', '
3989
							UPDATE {db_prefix}pm_recipients
3990
							SET in_inbox = {int:in_inbox}
3991
							WHERE id_pm = {int:id_pm}
3992
								AND id_member = {int:current_member}',
3993
							array(
3994
								'in_inbox' => 1,
3995
								'id_pm' => $pm,
3996
								'current_member' => $user_info['id'],
3997
							)
3998
						);
3999
					}
4000
					else
4001
					{
4002
						$realLabels[] = $label['id'];
4003
					}
4004
				}
4005
			}
4006
4007
			$inserts = array();
4008
			// Now we insert the label info
4009
			foreach ($realLabels as $a_label)
4010
				$inserts[] = array($pm, $a_label);
4011
4012
			$smcFunc['db_insert']('ignore',
4013
				'{db_prefix}pm_labeled_messages',
4014
				array('id_pm' => 'int', 'id_label' => 'int'),
4015
				$inserts,
4016
				array()
4017
			);
4018
		}
4019
	}
4020
}
4021
4022
/**
4023
 * Load up all the rules for the current user.
4024
 *
4025
 * @param bool $reload Whether or not to reload all the rules from the database if $context['rules'] is set
4026
 */
4027
function LoadRules($reload = false)
4028
{
4029
	global $user_info, $context, $smcFunc;
4030
4031
	if (isset($context['rules']) && !$reload)
4032
		return;
4033
4034
	$request = $smcFunc['db_query']('', '
4035
		SELECT
4036
			id_rule, rule_name, criteria, actions, delete_pm, is_or
4037
		FROM {db_prefix}pm_rules
4038
		WHERE id_member = {int:current_member}',
4039
		array(
4040
			'current_member' => $user_info['id'],
4041
		)
4042
	);
4043
	$context['rules'] = array();
4044
	// Simply fill in the data!
4045
	while ($row = $smcFunc['db_fetch_assoc']($request))
4046
	{
4047
		$context['rules'][$row['id_rule']] = array(
4048
			'id' => $row['id_rule'],
4049
			'name' => $row['rule_name'],
4050
			'criteria' => smf_json_decode($row['criteria'], true),
4051
			'actions' => smf_json_decode($row['actions'], true),
4052
			'delete' => $row['delete_pm'],
4053
			'logic' => $row['is_or'] ? 'or' : 'and',
4054
		);
4055
4056
		if ($row['delete_pm'])
4057
			$context['rules'][$row['id_rule']]['actions'][] = array('t' => 'del', 'v' => 1);
4058
	}
4059
	$smcFunc['db_free_result']($request);
4060
}
4061
4062
/**
4063
 * Check if the PM is available to the current user.
4064
 *
4065
 * @param int $pmID The ID of the PM
4066
 * @param string $validFor Which folders this is valud for - can be 'inbox', 'outbox' or 'in_or_outbox'
4067
 * @return boolean Whether the PM is accessible in that folder for the current user
4068
 */
4069
function isAccessiblePM($pmID, $validFor = 'in_or_outbox')
4070
{
4071
	global $user_info, $smcFunc;
4072
4073
	$request = $smcFunc['db_query']('', '
4074
		SELECT
4075
			pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted} AS valid_for_outbox,
4076
			pmr.id_pm IS NOT NULL AS valid_for_inbox
4077
		FROM {db_prefix}personal_messages AS pm
4078
			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})
4079
		WHERE pm.id_pm = {int:id_pm}
4080
			AND ((pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted}) OR pmr.id_pm IS NOT NULL)',
4081
		array(
4082
			'id_pm' => $pmID,
4083
			'id_current_member' => $user_info['id'],
4084
			'not_deleted' => 0,
4085
		)
4086
	);
4087
4088 View Code Duplication
	if ($smcFunc['db_num_rows']($request) === 0)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4089
	{
4090
		$smcFunc['db_free_result']($request);
4091
		return false;
4092
	}
4093
4094
	$validationResult = $smcFunc['db_fetch_assoc']($request);
4095
	$smcFunc['db_free_result']($request);
4096
4097
	switch ($validFor)
4098
	{
4099
		case 'inbox':
4100
			return !empty($validationResult['valid_for_inbox']);
4101
		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...
4102
4103
		case 'outbox':
4104
			return !empty($validationResult['valid_for_outbox']);
4105
		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...
4106
4107
		case 'in_or_outbox':
4108
			return !empty($validationResult['valid_for_inbox']) || !empty($validationResult['valid_for_outbox']);
4109
		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...
4110
4111
		default:
4112
			trigger_error('Undefined validation type given', E_USER_ERROR);
4113
		break;
4114
	}
4115
}
4116
4117
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...