Completed
Pull Request — master (#3325)
by Emanuele
11:19
created

PersonalMessage_Controller::_messageIndexBar()   C

Complexity

Conditions 12
Paths 60

Size

Total Lines 147
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 0
Metric Value
cc 12
eloc 84
dl 0
loc 147
rs 5.9224
c 0
b 0
f 0
nc 60
nop 1
ccs 0
cts 94
cp 0
crap 156

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.
6
 * For compatibility reasons, they are often called "instant messages".
7
 *
8
 * @name      ElkArte Forum
9
 * @copyright ElkArte Forum contributors
10
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
11
 *
12
 * This file contains code covered by:
13
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
14
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
15
 *
16
 * @version 1.1.4
17
 *
18
 */
19
20
use ElkArte\Errors\ErrorContext;
21
22
/**
23
 * PersonalMessage_Controller class
24
 * It allows viewing, sending, deleting, and marking personal messages
25
 *
26
 * @package PersonalMessage
27
 */
28
class PersonalMessage_Controller extends Action_Controller
29
{
30
	/**
31
	 * $_search_params will carry all settings that differ from the default
32
	 * search parameters. That way, the URLs involved in a search page will
33
	 * be kept as short as possible.
34
	 * @var array
35
	 */
36
	private $_search_params = array();
37
38
	/**
39
	 * $_searchq_parameters will carry all the values needed by S_search_params
40
	 * @var array
41
	 */
42
	private $_searchq_parameters = array();
43
44
	/**
45
	 * This method is executed before any other in this file (when the class is
46
	 * loaded by the dispatcher).
47
	 *
48
	 * What it does:
49
	 *
50
	 * - It sets the context, load templates and language file(s), as necessary
51
	 * for the function that will be called.
52
	 */
53
	public function pre_dispatch()
54
	{
55
		global $txt, $scripturl, $context, $user_info, $user_settings, $modSettings;
56
57
		// No guests!
58
		is_not_guest();
59
60
		// You're not supposed to be here at all, if you can't even read PMs.
61
		isAllowedTo('pm_read');
62
63
		// This file contains the our PM functions such as mark, send, delete
64
		require_once(SUBSDIR . '/PersonalMessage.subs.php');
65
66
		// Templates, language, javascripts
67
		loadLanguage('PersonalMessage');
68
		loadJavascriptFile(array('PersonalMessage.js', 'suggest.js'));
69
70
		if (!isset($this->_req->query->xml))
71
		{
72
			loadTemplate('PersonalMessage');
73
		}
74
75
		$this->_events->trigger('pre_dispatch', array('xml' => isset($this->_req->query->xml)));
76
77
		// Load up the members maximum message capacity.
78
		$this->_loadMessageLimit();
79
80
		// A previous message was sent successfully? show a small indication.
81
		if ($this->_req->getQuery('done') === 'sent')
82
		{
83
			$context['pm_sent'] = true;
84
		}
85
86
		// Load the label counts data.
87
		if ($user_settings['new_pm'] || !Cache::instance()->getVar($context['labels'], 'labelCounts:' . $user_info['id'], 720))
88
		{
89
			$this->_loadLabels();
90
91
			// Get the message count for each label
92
			$context['labels'] = loadPMLabels($context['labels']);
93
		}
94
95
		// Now we have the labels, and assuming we have unsorted mail, apply our rules!
96
		if ($user_settings['new_pm'])
97
		{
98
			// Apply our rules to the new PM's
99
			applyRules();
100
101
			require_once(SUBSDIR . '/Members.subs.php');
102
			updateMemberData($user_info['id'], array('new_pm' => 0));
103
104
			// Turn the new PM's status off, for the popup alert, since they have entered the PM area
105
			toggleNewPM($user_info['id']);
106
		}
107
108
		// This determines if we have more labels than just the standard inbox.
109
		$context['currently_using_labels'] = count($context['labels']) > 1 ? 1 : 0;
110
111
		// Some stuff for the labels...
112
		$context['current_label_id'] = isset($this->_req->query->l) && isset($context['labels'][(int) $this->_req->query->l]) ? (int) $this->_req->query->l : -1;
113
		$context['current_label'] = &$context['labels'][(int) $context['current_label_id']]['name'];
114
		$context['folder'] = !isset($this->_req->query->f) || $this->_req->query->f !== 'sent' ? 'inbox' : 'sent';
115
116
		// This is convenient.  Do you know how annoying it is to do this every time?!
117
		$context['current_label_redirect'] = 'action=pm;f=' . $context['folder'] . (isset($this->_req->query->start) ? ';start=' . $this->_req->query->start : '') . (isset($this->_req->query->l) ? ';l=' . $this->_req->query->l : '');
118
		$context['can_issue_warning'] = in_array('w', $context['admin_features']) && allowedTo('issue_warning') && !empty($modSettings['warning_enable']);
119
120
		// Build the linktree for all the actions...
121
		$context['linktree'][] = array(
122
			'url' => $scripturl . '?action=pm',
123
			'name' => $txt['personal_messages']
124
		);
125
126
		// Preferences...
127
		$context['display_mode'] = $user_settings['pm_prefs'] & 3;
128
	}
129
130
	/**
131
	 * Load a members message limit and prepares the limit bar
132
	 */
133
	private function _loadMessageLimit()
134
	{
135
		global $context, $txt, $user_info;
136
137
		$context['message_limit'] = loadMessageLimit();
138
139
		// Prepare the context for the capacity bar.
140
		if (!empty($context['message_limit']))
141
		{
142
			$bar = ($user_info['messages'] * 100) / $context['message_limit'];
143
144
			$context['limit_bar'] = array(
145
				'messages' => $user_info['messages'],
146
				'allowed' => $context['message_limit'],
147
				'percent' => $bar,
148
				'bar' => min(100, (int) $bar),
149
				'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], round($bar, 1)),
150
			);
151
		}
152
	}
153
154
	/**
155
	 * Loads the user defined label's for use in the template etc.
156
	 */
157
	private function _loadLabels()
158
	{
159
		global $context, $txt, $user_settings;
160
161
		$context['labels'] = $user_settings['message_labels'] === '' ? array() : explode(',', $user_settings['message_labels']);
162
163
		foreach ($context['labels'] as $id_label => $label_name)
164
		{
165
			$context['labels'][(int) $id_label] = array(
166
				'id' => $id_label,
167
				'name' => trim($label_name),
168
				'messages' => 0,
169
				'unread_messages' => 0,
170
			);
171
		}
172
173
		// The default inbox is always available
174
		$context['labels'][-1] = array(
175
			'id' => -1,
176
			'name' => $txt['pm_msg_label_inbox'],
177
			'messages' => 0,
178
			'unread_messages' => 0,
179
		);
180
	}
181
182
	/**
183
	 * This is the main function of personal messages, called before the action handler.
184
	 *
185
	 * What it does:
186
	 *
187
	 * - PersonalMessages is a menu-based controller.
188
	 * - It sets up the menu.
189
	 * - Calls from the menu the appropriate method/function for the current area.
190
	 *
191
	 * @see Action_Controller::action_index()
192
	 */
193
	public function action_index()
194
	{
195
		global $context;
196
197
		// Finally all the things we know how to do
198
		$subActions = array(
199
			'manlabels' => array($this, 'action_manlabels', 'permission' => 'pm_read'),
200
			'manrules' => array($this, 'action_manrules', 'permission' => 'pm_read'),
201
			'markunread' => array($this, 'action_markunread', 'permission' => 'pm_read'),
202
			'pmactions' => array($this, 'action_pmactions', 'permission' => 'pm_read'),
203
			'prune' => array($this, 'action_prune', 'permission' => 'pm_read'),
204
			'removeall' => array($this, 'action_removeall', 'permission' => 'pm_read'),
205
			'removeall2' => array($this, 'action_removeall2', 'permission' => 'pm_read'),
206
			'report' => array($this, 'action_report', 'permission' => 'pm_read'),
207
			'search' => array($this, 'action_search', 'permission' => 'pm_read'),
208
			'search2' => array($this, 'action_search2', 'permission' => 'pm_read'),
209
			'send' => array($this, 'action_send', 'permission' => 'pm_read'),
210
			'send2' => array($this, 'action_send2', 'permission' => 'pm_read'),
211
			'settings' => array($this, 'action_settings', 'permission' => 'pm_read'),
212
			'inbox' => array($this, 'action_folder', 'permission' => 'pm_read'),
213
		);
214
215
		// Set up our action array
216
		$action = new Action('pm_index');
217
218
		// Known action, go to it, otherwise the inbox for you
219
		$subAction = $action->initialize($subActions, 'inbox');
220
221
		// Set the right index bar for the action
222
		if ($subAction === 'inbox')
223
		{
224
			$this->_messageIndexBar($context['current_label_id'] == -1 ? $context['folder'] : 'label' . $context['current_label_id']);
225
		}
226
		elseif (!isset($this->_req->query->xml))
227
		{
228
			$this->_messageIndexBar($subAction);
229
		}
230
231
		// And off we go!
232
		$action->dispatch($subAction);
233
	}
234
235
	/**
236
	 * A menu to easily access different areas of the PM section
237
	 *
238
	 * @param string $area
239
	 *
240
	 * @throws Elk_Exception no_access
241
	 */
242
	private function _messageIndexBar($area)
243
	{
244
		global $txt, $context, $scripturl, $user_info;
245
246
		require_once(SUBSDIR . '/Menu.subs.php');
247
248
		$pm_areas = array(
249
			'folders' => array(
250
				'title' => $txt['pm_messages'],
251
				'counter' => 'unread_messages',
252
				'areas' => array(
253
					'inbox' => array(
254
						'label' => $txt['inbox'],
255
						'custom_url' => $scripturl . '?action=pm',
256
						'counter' => 'unread_messages',
257
					),
258
					'send' => array(
259
						'label' => $txt['new_message'],
260
						'custom_url' => $scripturl . '?action=pm;sa=send',
261
						'permission' => 'pm_send',
262
					),
263
					'sent' => array(
264
						'label' => $txt['sent_items'],
265
						'custom_url' => $scripturl . '?action=pm;f=sent',
266
					),
267
				),
268
			),
269
			'labels' => array(
270
				'title' => $txt['pm_labels'],
271
				'counter' => 'labels_unread_total',
272
				'areas' => array(),
273
			),
274
			'actions' => array(
275
				'title' => $txt['pm_actions'],
276
				'areas' => array(
277
					'search' => array(
278
						'label' => $txt['pm_search_bar_title'],
279
						'custom_url' => $scripturl . '?action=pm;sa=search',
280
					),
281
					'prune' => array(
282
						'label' => $txt['pm_prune'],
283
						'custom_url' => $scripturl . '?action=pm;sa=prune'
284
					),
285
				),
286
			),
287
			'pref' => array(
288
				'title' => $txt['pm_preferences'],
289
				'areas' => array(
290
					'manlabels' => array(
291
						'label' => $txt['pm_manage_labels'],
292
						'custom_url' => $scripturl . '?action=pm;sa=manlabels',
293
					),
294
					'manrules' => array(
295
						'label' => $txt['pm_manage_rules'],
296
						'custom_url' => $scripturl . '?action=pm;sa=manrules',
297
					),
298
					'settings' => array(
299
						'label' => $txt['pm_settings'],
300
						'custom_url' => $scripturl . '?action=pm;sa=settings',
301
					),
302
				),
303
			),
304
		);
305
306
		// Handle labels.
307
		$label_counters = array('unread_messages' => $context['labels'][-1]['unread_messages']);
308
		if (empty($context['currently_using_labels']))
309
		{
310
			unset($pm_areas['labels']);
311
		}
312
		else
313
		{
314
			// Note we send labels by id as it will have less problems in the query string.
315
			$label_counters['labels_unread_total'] = 0;
316
			foreach ($context['labels'] as $label)
317
			{
318
				if ($label['id'] == -1)
319
				{
320
					continue;
321
				}
322
323
				// Count the amount of unread items in labels.
324
				$label_counters['labels_unread_total'] += $label['unread_messages'];
325
326
				// Add the label to the menu.
327
				$pm_areas['labels']['areas']['label' . $label['id']] = array(
328
					'label' => $label['name'],
329
					'custom_url' => $scripturl . '?action=pm;l=' . $label['id'],
330
					'counter' => 'label' . $label['id'],
331
					'messages' => $label['messages'],
332
				);
333
334
				$label_counters['label' . $label['id']] = $label['unread_messages'];
335
			}
336
		}
337
338
		// Do we have a limit on the amount of messages we can keep?
339
		if (!empty($context['message_limit']))
340
		{
341
			$bar = round(($user_info['messages'] * 100) / $context['message_limit'], 1);
342
343
			$context['limit_bar'] = array(
344
				'messages' => $user_info['messages'],
345
				'allowed' => $context['message_limit'],
346
				'percent' => $bar,
347
				'bar' => $bar > 100 ? 100 : (int) $bar,
348
				'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], $bar)
349
			);
350
		}
351
352
		// Set a few options for the menu.
353
		$menuOptions = array(
354
			'current_area' => $area,
355
			'hook' => 'pm',
356
			'disable_url_session_check' => true,
357
			'counters' => !empty($label_counters) ? $label_counters : 0,
358
			'default_include_dir' => CONTROLLERDIR,
359
		);
360
361
		// Actually create the menu!
362
		$pm_include_data = createMenu($pm_areas, $menuOptions);
363
		unset($pm_areas);
364
365
		// No menu means no access.
366
		if (!$pm_include_data && (!$user_info['is_guest'] || validateSession() !== true))
367
		{
368
			throw new Elk_Exception('no_access', false);
369
		}
370
371
		// Make a note of the Unique ID for this menu.
372
		$context['pm_menu_id'] = $context['max_menu_id'];
373
		$context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id'];
374
375
		// Set the selected item.
376
		$context['menu_item_selected'] = $pm_include_data['current_area'];
377
378
		// Grab the file needed for this action
379
		if (isset($pm_include_data['file']))
380
		{
381
			require_once($pm_include_data['file']);
382
		}
383
384
		// Set the template for this area and add the profile layer.
385
		if (!isset($this->_req->query->xml))
386
		{
387
			$template_layers = Template_Layers::instance();
388
			$template_layers->add('pm');
389
		}
390
	}
391
392
	/**
393
	 * Display a folder, ie. inbox/sent etc.
394
	 *
395
	 * @uses folder sub template
396
	 * @uses subject_list, pm template layers
397
	 */
398
	public function action_folder()
399
	{
400
		global $txt, $scripturl, $modSettings, $context, $subjects_request;
401
		global $messages_request, $user_info, $recipients, $options, $user_settings;
402
403
		// Changing view?
404
		if (isset($this->_req->query->view))
405
		{
406
			$context['display_mode'] = $context['display_mode'] > 1 ? 0 : $context['display_mode'] + 1;
407
			require_once(SUBSDIR . '/Members.subs.php');
408
			updateMemberData($user_info['id'], array('pm_prefs' => ($user_settings['pm_prefs'] & 252) | $context['display_mode']));
409
		}
410
411
		// Make sure the starting location is valid.
412
		if (isset($this->_req->query->start) && $this->_req->query->start !== 'new')
413
		{
414
			$start = (int) $this->_req->query->start;
415
		}
416
		elseif (!isset($this->_req->query->start) && !empty($options['view_newest_pm_first']))
417
		{
418
			$start = 0;
419
		}
420
		else
421
		{
422
			$start = 'new';
423
		}
424
425
		// Set up some basic template stuff.
426
		$context['from_or_to'] = $context['folder'] !== 'sent' ? 'from' : 'to';
427
		$context['get_pmessage'] = 'preparePMContext_callback';
428
		$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
429
		$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
430
431
		// Set the template layers we need
432
		$template_layers = Template_Layers::instance();
433
		$template_layers->addAfter('subject_list', 'pm');
434
435
		$labelQuery = $context['folder'] !== 'sent' ? '
436
				AND FIND_IN_SET(' . $context['current_label_id'] . ', pmr.labels) != 0' : '';
437
438
		// They didn't pick a sort, so we use the forum default.
439
		$sort_by = !isset($this->_req->query->sort) ? 'date' : $this->_req->query->sort;
440
		$descending = isset($this->_req->query->desc);
441
442
		// Set our sort by query
443
		switch ($sort_by)
444
		{
445
			case 'date':
446
				$sort_by_query = 'pm.id_pm';
447
				if (!empty($options['view_newest_pm_first']) && !isset($this->_req->query->desc) && !isset($this->_req->query->asc))
448
				{
449
					$descending = true;
450
				}
451
				break;
452
			case 'name':
453
				$sort_by_query = 'COALESCE(mem.real_name, \'\')';
454
				break;
455
			case 'subject':
456
				$sort_by_query = 'pm.subject';
457
				break;
458
			default:
459
				$sort_by_query = 'pm.id_pm';
460
		}
461
462
		// Set the text to resemble the current folder.
463
		$pmbox = $context['folder'] !== 'sent' ? $txt['inbox'] : $txt['sent_items'];
464
		$txt['delete_all'] = str_replace('PMBOX', $pmbox, $txt['delete_all']);
465
466
		// Now, build the link tree!
467
		if ($context['current_label_id'] === -1)
468
		{
469
			$context['linktree'][] = array(
470
				'url' => $scripturl . '?action=pm;f=' . $context['folder'],
471
				'name' => $pmbox
472
			);
473
		}
474
475
		// Build it further if we also have a label.
476
		if ($context['current_label_id'] !== -1)
477
		{
478
			$context['linktree'][] = array(
479
				'url' => $scripturl . '?action=pm;f=' . $context['folder'] . ';l=' . $context['current_label_id'],
480
				'name' => $txt['pm_current_label'] . ': ' . $context['current_label']
481
			);
482
		}
483
484
		// Figure out how many messages there are.
485
		$max_messages = getPMCount(false, null, $labelQuery);
486
487
		// Only show the button if there are messages to delete.
488
		$context['show_delete'] = $max_messages > 0;
489
490
		// Start on the last page.
491
		if (!is_numeric($start) || $start >= $max_messages)
492
		{
493
			$start = ($max_messages - 1) - (($max_messages - 1) % $modSettings['defaultMaxMessages']);
494
		}
495
		elseif ($start < 0)
496
		{
497
			$start = 0;
498
		}
499
500
		// ... but wait - what if we want to start from a specific message?
501
		if (isset($this->_req->query->pmid))
502
		{
503
			$pmID = (int) $this->_req->query->pmid;
504
505
			// Make sure you have access to this PM.
506
			if (!isAccessiblePM($pmID, $context['folder'] === 'sent' ? 'outbox' : 'inbox'))
507
			{
508
				throw new Elk_Exception('no_access', false);
509
			}
510
511
			$context['current_pm'] = $pmID;
512
513
			// With only one page of PM's we're gonna want page 1.
514
			if ($max_messages <= $modSettings['defaultMaxMessages'])
515
			{
516
				$start = 0;
517
			}
518
			// If we pass kstart we assume we're in the right place.
519
			elseif (!isset($this->_req->query->kstart))
520
			{
521
				$start = getPMCount($descending, $pmID, $labelQuery);
522
523
				// To stop the page index's being abnormal, start the page on the page the message
524
				// would normally be located on...
525
				$start = $modSettings['defaultMaxMessages'] * (int) ($start / $modSettings['defaultMaxMessages']);
526
			}
527
		}
528
529
		// Sanitize and validate pmsg variable if set.
530
		if (isset($this->_req->query->pmsg))
531
		{
532
			$pmsg = (int) $this->_req->query->pmsg;
533
534
			if (!isAccessiblePM($pmsg, $context['folder'] === 'sent' ? 'outbox' : 'inbox'))
535
			{
536
				throw new Elk_Exception('no_access', false);
537
			}
538
		}
539
540
		// Determine the navigation context
541
		$context['links'] += array(
542
			'prev' => $start >= $modSettings['defaultMaxMessages'] ? $scripturl . '?action=pm;start=' . ($start - $modSettings['defaultMaxMessages']) : '',
543
			'next' => $start + $modSettings['defaultMaxMessages'] < $max_messages ? $scripturl . '?action=pm;start=' . ($start + $modSettings['defaultMaxMessages']) : '',
544
		);
545
		$context['page_info'] = array(
546
			'current_page' => $start / $modSettings['defaultMaxMessages'] + 1,
547
			'num_pages' => floor(($max_messages - 1) / $modSettings['defaultMaxMessages']) + 1
548
		);
549
550
		// We now know what they want, so lets fetch those PM's
551
		list ($pms, $posters, $recipients, $lastData) = loadPMs(array(
552
			'sort_by_query' => $sort_by_query,
553
			'display_mode' => $context['display_mode'],
554
			'sort_by' => $sort_by,
555
			'label_query' => $labelQuery,
556
			'pmsg' => isset($pmsg) ? (int) $pmsg : 0,
557
			'descending' => $descending,
558
			'start' => $start,
559
			'limit' => $modSettings['defaultMaxMessages'],
560
			'folder' => $context['folder'],
561
			'pmid' => isset($pmID) ? $pmID : 0,
562
		), $user_info['id']);
563
564
		// Make sure that we have been given a correct head pm id if we are in conversation mode
565
		if ($context['display_mode'] == 2 && !empty($pmID) && $pmID != $lastData['id'])
566
		{
567
			throw new Elk_Exception('no_access', false);
568
		}
569
570
		// If loadPMs returned results, lets show the pm subject list
571
		if (!empty($pms))
572
		{
573
			// Tell the template if no pm has specifically been selected
574
			if (empty($pmID))
575
			{
576
				$context['current_pm'] = 0;
577
			}
578
579
			// This is a list of the pm's that are used for "show all" display.
580
			if ($context['display_mode'] == 0)
581
			{
582
				$display_pms = $pms;
583
			}
584
			// Just use the last pm the user received to start things off
585
			else
586
			{
587
				$display_pms = array($lastData['id']);
588
			}
589
590
			// At this point we know the main id_pm's. But if we are looking at conversations we need
591
			// the PMs that make up the conversation
592
			if ($context['display_mode'] == 2)
593
			{
594
				list($display_pms, $posters) = loadConversationList($lastData['head'], $recipients, $context['folder']);
595
596
				// Conversation list may expose additional PM's being displayed
597
				$all_pms = array_unique(array_merge($pms, $display_pms));
598
599
				// See if any of these 'listing' PM's are in a conversation thread that has unread entries
600
				$context['conversation_unread'] = loadConversationUnreadStatus($all_pms);
601
			}
602
			// This is pretty much EVERY pm!
603
			else
604
			{
605
				$all_pms = array_unique(array_merge($pms, $display_pms));
606
			}
607
608
			// Get recipients (don't include bcc-recipients for your inbox, you're not supposed to know :P).
609
			list($context['message_labels'], $context['message_replied'], $context['message_unread']) = loadPMRecipientInfo($all_pms, $recipients, $context['folder']);
610
611
			// Make sure we don't load any unnecessary data for one at a time mode
612
			if ($context['display_mode'] == 1)
613
			{
614
				foreach ($posters as $pm_key => $sender)
615
				{
616
					if (!in_array($pm_key, $display_pms))
617
					{
618
						unset($posters[$pm_key]);
619
					}
620
				}
621
			}
622
623
			// Load some information about the message sender
624
			$posters = array_unique($posters);
625
			if (!empty($posters))
626
			{
627
				loadMemberData($posters);
628
			}
629
630
			// If we're on grouped/restricted view get a restricted list of messages.
631
			if ($context['display_mode'] != 0)
632
			{
633
				// Get the order right.
634
				$orderBy = array();
635
				foreach (array_reverse($pms) as $pm)
636
					$orderBy[] = 'pm.id_pm = ' . $pm;
637
638
				// Separate query for these bits, preparePMContext_callback will use it as required
639
				$subjects_request = loadPMSubjectRequest($pms, $orderBy);
640
			}
641
642
			// Execute the load message query if a message has been chosen and let
643
			// preparePMContext_callback fetch the results.  Otherwise just show the pm selection list
644
			if (empty($pmsg) && empty($pmID) && $context['display_mode'] != 0)
645
			{
646
				$messages_request = false;
647
			}
648
			else
649
			{
650
				$messages_request = loadPMMessageRequest($display_pms, $sort_by_query, $sort_by, $descending, $context['display_mode'], $context['folder']);
651
			}
652
		}
653
		else
654
		{
655
			$messages_request = false;
656
		}
657
658
		// Prepare some items for the template
659
		$context['can_send_pm'] = allowedTo('pm_send');
660
		$context['can_send_email'] = allowedTo('send_email_to_members');
661
		$context['sub_template'] = 'folder';
662
		$context['page_title'] = $txt['pm_inbox'];
663
		$context['sort_direction'] = $descending ? 'down' : 'up';
664
		$context['sort_by'] = $sort_by;
665
666
		// Auto video embedding enabled, someone may have a link in a PM
667
		if (!empty($messages_request) && !empty($modSettings['enableVideoEmbeding']))
668
		{
669
			addInlineJavascript('
670
		$(function() {
671
			$().linkifyvideo(oEmbedtext);
672
		});', true
673
			);
674
		}
675
676
		if (!empty($messages_request) && !empty($context['show_delete']))
677
		{
678
			Template_Layers::instance()->addEnd('pm_pages_and_buttons');
679
		}
680
681
		// Set up the page index.
682
		$context['page_index'] = constructPageIndex($scripturl . '?action=pm;f=' . $context['folder'] . (isset($this->_req->query->l) ? ';l=' . (int) $this->_req->query->l : '') . ';sort=' . $context['sort_by'] . ($descending ? ';desc' : ''), $start, $max_messages, $modSettings['defaultMaxMessages']);
0 ignored issues
show
Bug introduced by
It seems like $start can also be of type string; however, parameter $start of constructPageIndex() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

682
		$context['page_index'] = constructPageIndex($scripturl . '?action=pm;f=' . $context['folder'] . (isset($this->_req->query->l) ? ';l=' . (int) $this->_req->query->l : '') . ';sort=' . $context['sort_by'] . ($descending ? ';desc' : ''), /** @scrutinizer ignore-type */ $start, $max_messages, $modSettings['defaultMaxMessages']);
Loading history...
683
		$context['start'] = $start;
684
685
		$context['pm_form_url'] = $scripturl . '?action=pm;sa=pmactions;' . ($context['display_mode'] == 2 ? 'conversation;' : '') . 'f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '');
686
687
		// Finally mark the relevant messages as read.
688
		if ($context['folder'] !== 'sent' && !empty($context['labels'][(int) $context['current_label_id']]['unread_messages']))
689
		{
690
			// If the display mode is "old sk00l" do them all...
691
			if ($context['display_mode'] == 0)
692
			{
693
				markMessages(null, $context['current_label_id']);
694
			}
695
			// Otherwise do just the currently displayed ones!
696
			elseif (!empty($context['current_pm']))
697
			{
698
				markMessages($display_pms, $context['current_label_id']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $display_pms does not seem to be defined for all execution paths leading up to this point.
Loading history...
699
			}
700
		}
701
702
		// Build the conversation button array.
703
		if ($context['display_mode'] === 2 && !empty($context['current_pm']))
704
		{
705
			$context['conversation_buttons'] = array(
706
				'delete' => array(
707
					'text' => 'delete_conversation',
708
					'image' => 'delete.png',
709
					'lang' => true,
710
					'url' => $scripturl . '?action=pm;sa=pmactions;pm_actions%5B' . $context['current_pm'] . '%5D=delete;conversation;f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'],
711
					'custom' => 'onclick="return confirm(\'' . addslashes($txt['remove_message']) . '?\');"'
712
				),
713
			);
714
715
			// Allow mods to add additional buttons here
716
			call_integration_hook('integrate_conversation_buttons');
717
		}
718
	}
719
720
	/**
721
	 * Send a new personal message?
722
	 */
723
	public function action_send()
724
	{
725
		global $txt, $scripturl, $modSettings, $context, $user_info;
726
727
		// Load in some text and template dependencies
728
		loadLanguage('PersonalMessage');
729
		loadTemplate('PersonalMessage');
730
731
		// Set the template we will use
732
		$context['sub_template'] = 'send';
733
734
		// Extract out the spam settings - cause it's neat.
735
		list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
736
737
		// Set up some items for the template
738
		$context['page_title'] = $txt['send_message'];
739
		$context['reply'] = isset($this->_req->query->pmsg) || isset($this->_req->query->quote);
740
741
		// Check whether we've gone over the limit of messages we can send per hour.
742
		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')
743
		{
744
			// How many messages have they sent this last hour?
745
			$pmCount = pmCount($user_info['id'], 3600);
746
747
			if (!empty($pmCount) && $pmCount >= $modSettings['pm_posts_per_hour'])
748
			{
749
				throw new Elk_Exception('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
750
			}
751
		}
752
753
		try
754
		{
755
			$this->_events->trigger('before_set_context', array('pmsg' => isset($this->_req->query->pmsg) ? $this->_req->query->pmsg : (isset($this->_req->query->quote) ? $this->_req->query->quote : 0)));
756
		}
757
		catch (Pm_Error_Exception $e)
758
		{
759
			return $this->messagePostError($e->namedRecipientList, $e->recipientList, $e->msgOptions);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->messagePostError(...ntList, $e->msgOptions) targeting PersonalMessage_Controller::messagePostError() seems to always return null.

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

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

}

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

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

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

Loading history...
Bug introduced by
$e->msgOptions of type ElkArte\ValuesContainer is incompatible with the type array<mixed,mixed> expected by parameter $msg_options of PersonalMessage_Controller::messagePostError(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

759
			return $this->messagePostError($e->namedRecipientList, $e->recipientList, /** @scrutinizer ignore-type */ $e->msgOptions);
Loading history...
760
		}
761
762
		// Quoting / Replying to a message?
763
		if (!empty($this->_req->query->pmsg))
764
		{
765
			$pmsg = $this->_req->getQuery('pmsg', 'intval');
766
767
			// Make sure this is accessible (not deleted)
768
			if (!isAccessiblePM($pmsg))
769
			{
770
				throw new Elk_Exception('no_access', false);
771
			}
772
773
			// Validate that this is one has been received?
774
			$isReceived = checkPMReceived($pmsg);
775
776
			// Get the quoted message (and make sure you're allowed to see this quote!).
777
			$row_quoted = loadPMQuote($pmsg, $isReceived);
778
			if ($row_quoted === false)
779
			{
780
				throw new Elk_Exception('pm_not_yours', false);
781
			}
782
783
			// Censor the message.
784
			$row_quoted['subject'] = censor($row_quoted['subject']);
785
			$row_quoted['body'] = censor($row_quoted['body']);
786
787
			// Lets make sure we mark this one as read
788
			markMessages($pmsg);
789
790
			// Figure out which flavor or 'Re: ' to use
791
			$context['response_prefix'] = response_prefix();
792
793
			$form_subject = $row_quoted['subject'];
794
795
			// Add 'Re: ' to it....
796
			if ($context['reply'] && trim($context['response_prefix']) != '' && Util::strpos($form_subject, trim($context['response_prefix'])) !== 0)
797
			{
798
				$form_subject = $context['response_prefix'] . $form_subject;
799
			}
800
801
			// If quoting, lets clean up some things and set the quote header for the pm body
802
			if (isset($this->_req->query->quote))
803
			{
804
				// Remove any nested quotes and <br />...
805
				$form_message = preg_replace('~<br ?/?' . '>~i', "\n", $row_quoted['body']);
806
				$form_message = removeNestedQuotes($form_message);
807
808
				if (empty($row_quoted['id_member']))
809
				{
810
					$form_message = '[quote author=&quot;' . $row_quoted['real_name'] . '&quot;]' . "\n" . $form_message . "\n" . '[/quote]';
811
				}
812
				else
813
				{
814
					$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]';
815
				}
816
			}
817
			else
818
			{
819
				$form_message = '';
820
			}
821
822
			// Do the BBC thang on the message.
823
			$bbc_parser = \BBC\ParserWrapper::instance();
824
			$row_quoted['body'] = $bbc_parser->parsePM($row_quoted['body']);
825
826
			// Set up the quoted message array.
827
			$context['quoted_message'] = array(
828
				'id' => $row_quoted['id_pm'],
829
				'pm_head' => $row_quoted['pm_head'],
830
				'member' => array(
831
					'name' => $row_quoted['real_name'],
832
					'username' => $row_quoted['member_name'],
833
					'id' => $row_quoted['id_member'],
834
					'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
835
					'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'],
836
				),
837
				'subject' => $row_quoted['subject'],
838
				'time' => standardTime($row_quoted['msgtime']),
839
				'html_time' => htmlTime($row_quoted['msgtime']),
840
				'timestamp' => forum_time(true, $row_quoted['msgtime']),
841
				'body' => $row_quoted['body']
842
			);
843
		}
844
		// A new message it is then
845
		else
846
		{
847
			$context['quoted_message'] = false;
848
			$form_subject = '';
849
			$form_message = '';
850
		}
851
852
		// Start of like we don't know where this is going
853
		$context['recipients'] = array(
854
			'to' => array(),
855
			'bcc' => array(),
856
		);
857
858
		// Sending by ID?  Replying to all?  Fetch the real_name(s).
859
		if (isset($this->_req->query->u))
860
		{
861
			// If the user is replying to all, get all the other members this was sent to..
862
			if ($this->_req->query->u === 'all' && isset($row_quoted))
863
			{
864
				// Firstly, to reply to all we clearly already have $row_quoted - so have the original member from.
865
				if ($row_quoted['id_member'] != $user_info['id'])
866
				{
867
					$context['recipients']['to'][] = array(
868
						'id' => $row_quoted['id_member'],
869
						'name' => htmlspecialchars($row_quoted['real_name'], ENT_COMPAT, 'UTF-8'),
870
					);
871
				}
872
873
				// Now to get all the others.
874
				$context['recipients']['to'] = array_merge($context['recipients']['to'], isset($pmsg) ? loadPMRecipientsAll($pmsg) : array());
875
			}
876
			else
877
			{
878
				$users = array_map('intval', explode(',', $this->_req->query->u));
879
				$users = array_unique($users);
880
881
				// For all the member's this is going to, get their display name.
882
				require_once(SUBSDIR . '/Members.subs.php');
883
				$result = getBasicMemberData($users);
884
885
				foreach ($result as $row)
886
				{
887
					$context['recipients']['to'][] = array(
888
						'id' => $row['id_member'],
889
						'name' => $row['real_name'],
890
					);
891
				}
892
			}
893
894
			// Get a literal name list in case the user has JavaScript disabled.
895
			$names = array();
896
			foreach ($context['recipients']['to'] as $to)
897
				$names[] = $to['name'];
898
			$context['to_value'] = empty($names) ? '' : '&quot;' . implode('&quot;, &quot;', $names) . '&quot;';
899
		}
900
		else
901
		{
902
			$context['to_value'] = '';
903
		}
904
905
		// Set the defaults...
906
		$context['subject'] = $form_subject;
907
		$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
908
909
		// And build the link tree.
910
		$context['linktree'][] = array(
911
			'url' => $scripturl . '?action=pm;sa=send',
912
			'name' => $txt['new_message']
913
		);
914
915
		// Needed for the editor.
916
		require_once(SUBSDIR . '/Editor.subs.php');
917
918
		// Now create the editor.
919
		$editorOptions = array(
920
			'id' => 'message',
921
			'value' => $context['message'],
922
			'height' => '250px',
923
			'width' => '100%',
924
			'labels' => array(
925
				'post_button' => $txt['send_message'],
926
			),
927
			'preview_type' => 2,
928
		);
929
930
		// Trigger the prepare_send_context PM event
931
		$this->_events->trigger('prepare_send_context', array('pmsg' => isset($this->_req->query->pmsg) ? $this->_req->query->pmsg : (isset($this->_req->query->quote) ? $this->_req->query->quote : 0), 'editorOptions' => &$editorOptions, 'recipientList' => &$context['recipients']));
932
933
		create_control_richedit($editorOptions);
934
935
		// No one is bcc'ed just yet
936
		$context['bcc_value'] = '';
937
938
		// Register this form and get a sequence number in $context.
939
		checkSubmitOnce('register');
940
	}
941
942
	/**
943
	 * Send a personal message.
944
	 */
945
	public function action_send2()
946
	{
947
		global $txt, $context, $user_info, $modSettings;
948
949
		// All the helpers we need
950
		require_once(SUBSDIR . '/Auth.subs.php');
951
		require_once(SUBSDIR . '/Post.subs.php');
952
953
		loadLanguage('PersonalMessage', '', false);
954
955
		// Extract out the spam settings - it saves database space!
956
		list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
957
958
		// Initialize the errors we're about to make.
959
		$post_errors = ErrorContext::context('pm', 1);
960
961
		// Check whether we've gone over the limit of messages we can send per hour - fatal error if fails!
962
		if (!empty($modSettings['pm_posts_per_hour'])
963
			&& !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail'))
964
			&& $user_info['mod_cache']['bq'] === '0=1'
965
			&& $user_info['mod_cache']['gq'] === '0=1'
966
		)
967
		{
968
			// How many have they sent this last hour?
969
			$pmCount = pmCount($user_info['id'], 3600);
970
971
			if (!empty($pmCount) && $pmCount >= $modSettings['pm_posts_per_hour'])
972
			{
973
				if (!isset($this->_req->query->xml))
974
				{
975
					throw new Elk_Exception('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
976
				}
977
				else
978
				{
979
					$post_errors->addError('pm_too_many_per_hour');
980
				}
981
			}
982
		}
983
984
		// If your session timed out, show an error, but do allow to re-submit.
985
		if (!isset($this->_req->query->xml) && checkSession('post', '', false) != '')
986
		{
987
			$post_errors->addError('session_timeout');
988
		}
989
990
		$this->_req->post->subject = isset($this->_req->post->subject) ? strtr(Util::htmltrim($this->_req->post->subject), array("\r" => '', "\n" => '', "\t" => '')) : '';
991
		$this->_req->post->to = $this->_req->getPost('to', 'trim', empty($this->_req->query->to) ? '' : $this->_req->query->to);
992
		$this->_req->post->bcc = $this->_req->getPost('bcc', 'trim', empty($this->_req->query->bcc) ? '' : $this->_req->query->bcc);
993
994
		// Route the input from the 'u' parameter to the 'to'-list.
995
		if (!empty($this->_req->post->u))
996
		{
997
			$this->_req->post->recipient_to = explode(',', $this->_req->post->u);
998
		}
999
1000
		$bbc_parser = \BBC\ParserWrapper::instance();
1001
1002
		// Construct the list of recipients.
1003
		$recipientList = array();
1004
		$namedRecipientList = array();
1005
		$namesNotFound = array();
1006
		foreach (array('to', 'bcc') as $recipientType)
1007
		{
1008
			// First, let's see if there's user ID's given.
1009
			$recipientList[$recipientType] = array();
1010
			$type = 'recipient_' . $recipientType;
1011
			if (!empty($this->_req->post->{$type}) && is_array($this->_req->post->{$type}))
1012
			{
1013
				$recipientList[$recipientType] = array_map('intval', $this->_req->post->{$type});
1014
			}
1015
1016
			// Are there also literal names set?
1017
			if (!empty($this->_req->post->{$recipientType}))
1018
			{
1019
				// We're going to take out the "s anyway ;).
1020
				$recipientString = strtr($this->_req->post->{$recipientType}, array('\\"' => '"'));
1021
1022
				preg_match_all('~"([^"]+)"~', $recipientString, $matches);
1023
				$namedRecipientList[$recipientType] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))));
1024
1025
				// Clean any literal names entered
1026
				foreach ($namedRecipientList[$recipientType] as $index => $recipient)
1027
				{
1028
					if (strlen(trim($recipient)) > 0)
1029
					{
1030
						$namedRecipientList[$recipientType][$index] = Util::htmlspecialchars(Util::strtolower(trim($recipient)));
1031
					}
1032
					else
1033
					{
1034
						unset($namedRecipientList[$recipientType][$index]);
1035
					}
1036
				}
1037
1038
				// Now see if we can resolve the entered name to an actual user
1039
				if (!empty($namedRecipientList[$recipientType]))
1040
				{
1041
					$foundMembers = findMembers($namedRecipientList[$recipientType]);
1042
1043
					// Assume all are not found, until proven otherwise.
1044
					$namesNotFound[$recipientType] = $namedRecipientList[$recipientType];
1045
1046
					// Make sure we only have each member listed once, in case they did not use the select list
1047
					foreach ($foundMembers as $member)
1048
					{
1049
						$testNames = array(
1050
							Util::strtolower($member['username']),
1051
							Util::strtolower($member['name']),
1052
							Util::strtolower($member['email']),
1053
						);
1054
1055
						if (count(array_intersect($testNames, $namedRecipientList[$recipientType])) !== 0)
1056
						{
1057
							$recipientList[$recipientType][] = $member['id'];
1058
1059
							// Get rid of this username, since we found it.
1060
							$namesNotFound[$recipientType] = array_diff($namesNotFound[$recipientType], $testNames);
1061
						}
1062
					}
1063
				}
1064
			}
1065
1066
			// Selected a recipient to be deleted? Remove them now.
1067
			if (!empty($this->_req->post->delete_recipient))
1068
			{
1069
				$recipientList[$recipientType] = array_diff($recipientList[$recipientType], array((int) $this->_req->post->delete_recipient));
1070
			}
1071
1072
			// Make sure we don't include the same name twice
1073
			$recipientList[$recipientType] = array_unique($recipientList[$recipientType]);
1074
		}
1075
1076
		// Are we changing the recipients some how?
1077
		$is_recipient_change = !empty($this->_req->post->delete_recipient) || !empty($this->_req->post->to_submit) || !empty($this->_req->post->bcc_submit);
1078
1079
		// Check if there's at least one recipient.
1080
		if (empty($recipientList['to']) && empty($recipientList['bcc']))
1081
		{
1082
			$post_errors->addError('no_to');
1083
		}
1084
1085
		// Make sure that we remove the members who did get it from the screen.
1086
		if (!$is_recipient_change)
1087
		{
1088
			foreach (array_keys($recipientList) as $recipientType)
1089
			{
1090
				if (!empty($namesNotFound[$recipientType]))
1091
				{
1092
					$post_errors->addError('bad_' . $recipientType);
1093
1094
					// Since we already have a post error, remove the previous one.
1095
					$post_errors->removeError('no_to');
1096
1097
					foreach ($namesNotFound[$recipientType] as $name)
1098
						$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
1099
				}
1100
			}
1101
		}
1102
1103
		// Did they make any mistakes like no subject or message?
1104
		if ($this->_req->post->subject === '')
1105
		{
1106
			$post_errors->addError('no_subject');
1107
		}
1108
1109
		if (!isset($this->_req->post->message) || $this->_req->post->message === '')
1110
		{
1111
			$post_errors->addError('no_message');
1112
		}
1113
		elseif (!empty($modSettings['max_messageLength']) && Util::strlen($this->_req->post->message) > $modSettings['max_messageLength'])
1114
		{
1115
			$post_errors->addError('long_message');
1116
		}
1117
		else
1118
		{
1119
			// Preparse the message.
1120
			$message = $this->_req->post->message;
1121
			preparsecode($message);
1122
1123
			// Make sure there's still some content left without the tags.
1124
			if (Util::htmltrim(strip_tags($bbc_parser->parsePM(Util::htmlspecialchars($message, ENT_QUOTES)), '<img>')) === '' && (!allowedTo('admin_forum') || strpos($message, '[html]') === false))
1125
			{
1126
				$post_errors->addError('no_message');
1127
			}
1128
		}
1129
1130
		// If they made any errors, give them a chance to make amends.
1131
		if ($post_errors->hasErrors() && !$is_recipient_change && !isset($this->_req->query->preview) && !isset($this->_req->query->xml))
1132
		{
1133
			$this->messagePostError($namedRecipientList, $recipientList);
1134
1135
			return false;
1136
		}
1137
1138
		// Want to take a second glance before you send?
1139
		if (isset($this->_req->query->preview))
1140
		{
1141
			// Set everything up to be displayed.
1142
			$context['preview_subject'] = Util::htmlspecialchars($this->_req->post->subject);
1143
			$context['preview_message'] = Util::htmlspecialchars($this->_req->post->message, ENT_QUOTES, 'UTF-8', true);
1144
			preparsecode($context['preview_message'], true);
1145
1146
			// Parse out the BBC if it is enabled.
1147
			$context['preview_message'] = $bbc_parser->parsePM($context['preview_message']);
1148
1149
			// Censor, as always.
1150
			$context['preview_subject'] = censor($context['preview_subject']);
1151
			$context['preview_message'] = censor($context['preview_message']);
1152
1153
			// Set a descriptive title.
1154
			$context['page_title'] = $txt['preview'] . ' - ' . $context['preview_subject'];
1155
1156
			// Pretend they messed up but don't ignore if they really did :P.
1157
			$this->messagePostError($namedRecipientList, $recipientList);
1158
1159
			return false;
1160
		}
1161
		// Adding a recipient cause javascript ain't working?
1162
		elseif ($is_recipient_change)
1163
		{
1164
			// Maybe we couldn't find one?
1165
			foreach ($namesNotFound as $recipientType => $names)
1166
			{
1167
				$post_errors->addError('bad_' . $recipientType);
1168
				foreach ($names as $name)
1169
					$context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
1170
			}
1171
1172
			$this->messagePostError($namedRecipientList, $recipientList);
1173
			return true;
1174
		}
1175
1176
		try
1177
		{
1178
			$this->_events->trigger('before_sending', array('namedRecipientList' => $namedRecipientList, 'recipientList' => $recipientList, 'namesNotFound' => $namesNotFound, 'post_errors' => $post_errors));
1179
		}
1180
		catch (Controller_Redirect_Exception $e)
1181
		{
1182
			return $this->messagePostError($namedRecipientList, $recipientList);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->messagePostError(...ntList, $recipientList) targeting PersonalMessage_Controller::messagePostError() seems to always return null.

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

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

}

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

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

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

Loading history...
1183
		}
1184
1185
		// Safety net, it may be a module may just add to the list of errors without actually throw the error
1186
		if ($post_errors->hasErrors() && !isset($this->_req->query->preview) && !isset($this->_req->query->xml))
1187
		{
1188
			$this->messagePostError($namedRecipientList, $recipientList);
1189
1190
			return false;
1191
		}
1192
1193
		// Before we send the PM, let's make sure we don't have an abuse of numbers.
1194
		if (!empty($modSettings['max_pm_recipients']) && count($recipientList['to']) + count($recipientList['bcc']) > $modSettings['max_pm_recipients'] && !allowedTo(array('moderate_forum', 'send_mail', 'admin_forum')))
1195
		{
1196
			$context['send_log'] = array(
1197
				'sent' => array(),
1198
				'failed' => array(sprintf($txt['pm_too_many_recipients'], $modSettings['max_pm_recipients'])),
1199
			);
1200
1201
			$this->messagePostError($namedRecipientList, $recipientList);
1202
			return false;
1203
		}
1204
1205
		// Protect from message spamming.
1206
		spamProtection('pm');
1207
1208
		// Prevent double submission of this form.
1209
		checkSubmitOnce('check');
1210
1211
		// Finally do the actual sending of the PM.
1212
		if (!empty($recipientList['to']) || !empty($recipientList['bcc']))
1213
		{
1214
			$context['send_log'] = sendpm($recipientList, $this->_req->post->subject, $this->_req->post->message, true, null, !empty($this->_req->post->pm_head) ? (int) $this->_req->post->pm_head : 0);
1215
		}
1216
		else
1217
		{
1218
			$context['send_log'] = array(
1219
				'sent' => array(),
1220
				'failed' => array()
1221
			);
1222
		}
1223
1224
		// Mark the message as "replied to".
1225
		if (!empty($context['send_log']['sent']) && !empty($this->_req->post->replied_to) && $this->_req->getQuery('f') === 'inbox')
1226
		{
1227
			require_once(SUBSDIR . '/PersonalMessage.subs.php');
1228
			setPMRepliedStatus($user_info['id'], (int) $this->_req->post->replied_to);
1229
		}
1230
1231
		$failed = !empty($context['send_log']['failed']);
1232
		$this->_events->trigger('message_sent', array('failed' => $failed));
1233
1234
		// If one or more of the recipients were invalid, go back to the post screen with the failed usernames.
1235
		if ($failed)
1236
		{
1237
			$this->messagePostError($namesNotFound, array(
1238
				'to' => array_intersect($recipientList['to'], $context['send_log']['failed']),
1239
				'bcc' => array_intersect($recipientList['bcc'], $context['send_log']['failed'])
1240
			));
1241
1242
			return false;
1243
		}
1244
		// Message sent successfully?
1245
		else
1246
		{
1247
			$context['current_label_redirect'] = $context['current_label_redirect'] . ';done=sent';
1248
		}
1249
1250
		// Go back to the where they sent from, if possible...
1251
		redirectexit($context['current_label_redirect']);
1252
1253
		return true;
1254
	}
1255
1256
	/**
1257
	 * An error in the message...
1258
	 *
1259
	 * @param mixed[] $named_recipients
1260
	 * @param mixed[] $recipient_ids array keys of [bbc] => int[] and [to] => int[]
1261
	 * @param mixed[] $msg_options body, subject and reply values
1262
	 *
1263
	 * @throws Elk_Exception pm_not_yours
1264
	 */
1265
	public function messagePostError($named_recipients, $recipient_ids = array(), $msg_options = null)
1266
	{
1267
		global $txt, $context, $scripturl, $modSettings, $user_info;
1268
1269
		if (isset($this->_req->query->xml))
1270
		{
1271
			$context['sub_template'] = 'generic_preview';
1272
		}
1273
		else
1274
		{
1275
			$context['sub_template'] = 'send';
1276
			$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'send';
1277
		}
1278
1279
		$context['page_title'] = $txt['send_message'];
1280
		$error_types = ErrorContext::context('pm', 1);
1281
1282
		// Got some known members?
1283
		$context['recipients'] = array(
1284
			'to' => array(),
1285
			'bcc' => array(),
1286
		);
1287
1288
		if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc']))
1289
		{
1290
			$allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']);
1291
1292
			require_once(SUBSDIR . '/Members.subs.php');
1293
1294
			// Get the latest activated member's display name.
1295
			$result = getBasicMemberData($allRecipients);
1296
			foreach ($result as $row)
1297
			{
1298
				$recipientType = in_array($row['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to';
1299
				$context['recipients'][$recipientType][] = array(
1300
					'id' => $row['id_member'],
1301
					'name' => $row['real_name'],
1302
				);
1303
			}
1304
		}
1305
1306
		// Set everything up like before....
1307
		if (!empty($msg_options))
1308
		{
1309
			$context['subject'] = $msg_options->subject;
1310
			$context['message'] = $msg_options->body;
1311
			$context['reply'] = $msg_options->reply_to;
1312
		}
1313
		else
1314
		{
1315
			$context['subject'] = isset($this->_req->post->subject) ? Util::htmlspecialchars($this->_req->post->subject) : '';
1316
			$context['message'] = isset($this->_req->post->message) ? str_replace(array('  '), array('&nbsp; '), Util::htmlspecialchars($this->_req->post->message, ENT_QUOTES, 'UTF-8', true)) : '';
1317
			$context['reply'] = !empty($this->_req->post->replied_to);
1318
		}
1319
1320
		// If this is a reply to message, we need to reload the quote
1321
		if ($context['reply'])
1322
		{
1323
			$pmsg = (int) $this->_req->post->replied_to;
1324
			$isReceived = $context['folder'] !== 'sent';
1325
			$row_quoted = loadPMQuote($pmsg, $isReceived);
1326
			if ($row_quoted === false)
1327
			{
1328
				if (!isset($this->_req->query->xml))
1329
				{
1330
					throw new Elk_Exception('pm_not_yours', false);
1331
				}
1332
				else
1333
				{
1334
					$error_types->addError('pm_not_yours');
1335
				}
1336
			}
1337
			else
1338
			{
1339
				$row_quoted['subject'] = censor($row_quoted['subject']);
1340
				$row_quoted['body'] = censor($row_quoted['body']);
1341
				$bbc_parser = \BBC\ParserWrapper::instance();
1342
1343
				$context['quoted_message'] = array(
1344
					'id' => $row_quoted['id_pm'],
1345
					'pm_head' => $row_quoted['pm_head'],
1346
					'member' => array(
1347
						'name' => $row_quoted['real_name'],
1348
						'username' => $row_quoted['member_name'],
1349
						'id' => $row_quoted['id_member'],
1350
						'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
1351
						'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'],
1352
					),
1353
					'subject' => $row_quoted['subject'],
1354
					'time' => standardTime($row_quoted['msgtime']),
1355
					'html_time' => htmlTime($row_quoted['msgtime']),
1356
					'timestamp' => forum_time(true, $row_quoted['msgtime']),
1357
					'body' => $bbc_parser->parsePM($row_quoted['body']),
1358
				);
1359
			}
1360
		}
1361
1362
		// Build the link tree....
1363
		$context['linktree'][] = array(
1364
			'url' => $scripturl . '?action=pm;sa=send',
1365
			'name' => $txt['new_message']
1366
		);
1367
1368
		// Set each of the errors for the template.
1369
		$context['post_error'] = array(
1370
			'errors' => $error_types->prepareErrors(),
1371
			'type' => $error_types->getErrorType() == 0 ? 'minor' : 'serious',
1372
			'title' => $txt['error_while_submitting'],
1373
		);
1374
1375
		// We need to load the editor once more.
1376
		require_once(SUBSDIR . '/Editor.subs.php');
1377
1378
		// Create it...
1379
		$editorOptions = array(
1380
			'id' => 'message',
1381
			'value' => $context['message'],
1382
			'width' => '100%',
1383
			'height' => '250px',
1384
			'labels' => array(
1385
				'post_button' => $txt['send_message'],
1386
			),
1387
			'preview_type' => 2,
1388
		);
1389
1390
		// Trigger the prepare_send_context PM event
1391
		$this->_events->trigger('prepare_send_context', array('pmsg' => isset($this->_req->query->pmsg) ? $this->_req->query->pmsg : (isset($this->_req->query->quote) ? $this->_req->query->quote : 0), 'editorOptions' => &$editorOptions, 'recipientList' => &$recipient_ids));
1392
1393
		create_control_richedit($editorOptions);
1394
1395
		// Check whether we need to show the code again.
1396
		$context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
1397
		if ($context['require_verification'] && !isset($this->_req->query->xml))
1398
		{
1399
			require_once(SUBSDIR . '/VerificationControls.class.php');
1400
			$verificationOptions = array(
1401
				'id' => 'pm',
1402
			);
1403
			$context['require_verification'] = create_control_verification($verificationOptions);
1404
			$context['visual_verification_id'] = $verificationOptions['id'];
1405
		}
1406
1407
		$context['to_value'] = empty($named_recipients['to']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['to']) . '&quot;';
1408
		$context['bcc_value'] = empty($named_recipients['bcc']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['bcc']) . '&quot;';
1409
1410
		// No check for the previous submission is needed.
1411
		checkSubmitOnce('free');
1412
1413
		// Acquire a new form sequence number.
1414
		checkSubmitOnce('register');
1415
	}
1416
1417
	/**
1418
	 * This function performs all additional actions including the deleting
1419
	 * and labeling of PM's
1420
	 */
1421
	public function action_pmactions()
1422
	{
1423
		global $context, $user_info;
1424
1425
		checkSession('request');
1426
1427
		// Sending in the single pm choice via GET
1428
		$pm_actions = $this->_req->getQuery('pm_actions', null, '');
1429
1430
		// Set the action to apply to the pm's defined by pm_actions (yes its that brilliant)
1431
		$pm_action = $this->_req->getPost('pm_action', 'trim', '');
1432
		$pm_action = empty($pm_action) && isset($this->_req->post->del_selected) ? 'delete' : $pm_action;
1433
1434
		// Create a list of pm's that we need to work on
1435
		if ($pm_action != ''
1436
			&& !empty($this->_req->post->pms)
1437
			&& is_array($this->_req->post->pms))
1438
		{
1439
			$pm_actions = array();
1440
			foreach ($this->_req->post->pms as $pm)
1441
				$pm_actions[(int) $pm] = $pm_action;
1442
		}
1443
1444
		// No messages to action then bug out
1445
		if (empty($pm_actions))
1446
			redirectexit($context['current_label_redirect']);
1447
1448
		// If we are in conversation, we may need to apply this to every message in that conversation.
1449
		if ($context['display_mode'] == 2 && isset($this->_req->query->conversation))
1450
		{
1451
			$id_pms = array_map('intval', array_keys($pm_actions));
1452
			$pm_heads = getDiscussions($id_pms);
1453
			$pms = getPmsFromDiscussion(array_keys($pm_heads));
1454
1455
			// Copy the action from the single to PM to the others in the conversation.
1456
			foreach ($pms as $id_pm => $id_head)
1457
			{
1458
				if (isset($pm_heads[$id_head]) && isset($pm_actions[$pm_heads[$id_head]]))
1459
				{
1460
					$pm_actions[$id_pm] = $pm_actions[$pm_heads[$id_head]];
1461
				}
1462
			}
1463
		}
1464
1465
		// Lets get to doing what we've been told
1466
		$to_delete = array();
1467
		$to_label = array();
1468
		$label_type = array();
1469
		foreach ($pm_actions as $pm => $action)
1470
		{
1471
			// What are we doing with the selected messages, adding a label, removing, other?
1472
			switch (substr($action, 0, 4))
1473
			{
1474
				case 'dele':
1475
					$to_delete[] = (int) $pm;
1476
					break;
1477
				case 'add_':
1478
					$type = 'add';
1479
					$action = substr($action, 4);
1480
					break;
1481
				case 'rem_':
1482
					$type = 'rem';
1483
					$action = substr($action, 4);
1484
					break;
1485
				default:
1486
					$type = 'unk';
1487
			}
1488
1489
			if ($action === '-1' || $action === '0' || (int) $action > 0)
1490
			{
1491
				$to_label[(int) $pm] = (int) $action;
1492
				$label_type[(int) $pm] = $type;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.
Loading history...
1493
			}
1494
		}
1495
1496
		// Deleting, it looks like?
1497
		if (!empty($to_delete))
1498
		{
1499
			deleteMessages($to_delete, $context['display_mode'] == 2 ? null : $context['folder']);
1500
		}
1501
1502
		// Are we labelling anything?
1503
		if (!empty($to_label) && $context['folder'] === 'inbox')
1504
		{
1505
			$updateErrors = changePMLabels($to_label, $label_type, $user_info['id']);
1506
1507
			// Any errors?
1508
			if (!empty($updateErrors))
1509
			{
1510
				throw new Elk_Exception('labels_too_many', true, array($updateErrors));
1511
			}
1512
		}
1513
1514
		// Back to the folder.
1515
		$_SESSION['pm_selected'] = array_keys($to_label);
1516
		redirectexit($context['current_label_redirect'] . (count($to_label) == 1 ? '#msg_' . $_SESSION['pm_selected'][0] : ''), count($to_label) == 1 && isBrowser('ie'));
1517
	}
1518
1519
	/**
1520
	 * Are you sure you want to PERMANENTLY (mostly) delete ALL your messages?
1521
	 */
1522
	public function action_removeall()
1523
	{
1524
		global $txt, $context;
1525
1526
		// Only have to set up the template....
1527
		$context['sub_template'] = 'ask_delete';
1528
		$context['page_title'] = $txt['delete_all'];
1529
		$context['delete_all'] = $this->_req->query->f === 'all';
1530
1531
		// And set the folder name...
1532
		$txt['delete_all'] = str_replace('PMBOX', $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items'], $txt['delete_all']);
1533
	}
1534
1535
	/**
1536
	 * Delete ALL the messages!
1537
	 */
1538
	public function action_removeall2()
1539
	{
1540
		global $context;
1541
1542
		checkSession('get');
1543
1544
		// If all then delete all messages the user has.
1545
		if ($this->_req->query->f === 'all')
1546
		{
1547
			deleteMessages(null, null);
1548
		}
1549
		// Otherwise just the selected folder.
1550
		else
1551
		{
1552
			deleteMessages(null, $this->_req->query->f != 'sent' ? 'inbox' : 'sent');
1553
		}
1554
1555
		// Done... all gone.
1556
		redirectexit($context['current_label_redirect']);
1557
	}
1558
1559
	/**
1560
	 * This function allows the user to prune (delete) all messages older than a supplied duration.
1561
	 */
1562
	public function action_prune()
1563
	{
1564
		global $txt, $context, $user_info, $scripturl;
1565
1566
		// Actually delete the messages.
1567
		if (isset($this->_req->post->age))
1568
		{
1569
			checkSession();
1570
1571
			// Calculate the time to delete before.
1572
			$deleteTime = max(0, time() - (86400 * (int) $this->_req->post->age));
1573
1574
			// Select all the messages older than $deleteTime.
1575
			$toDelete = getPMsOlderThan($user_info['id'], $deleteTime);
1576
1577
			// Delete the actual messages.
1578
			deleteMessages($toDelete);
1579
1580
			// Go back to their inbox.
1581
			redirectexit($context['current_label_redirect']);
1582
		}
1583
1584
		// Build the link tree elements.
1585
		$context['linktree'][] = array(
1586
			'url' => $scripturl . '?action=pm;sa=prune',
1587
			'name' => $txt['pm_prune']
1588
		);
1589
		$context['sub_template'] = 'prune';
1590
		$context['page_title'] = $txt['pm_prune'];
1591
	}
1592
1593
	/**
1594
	 * This function handles adding, deleting and editing labels on messages.
1595
	 */
1596
	public function action_manlabels()
1597
	{
1598
		global $txt, $context, $user_info, $scripturl;
1599
1600
		require_once(SUBSDIR . '/PersonalMessage.subs.php');
1601
1602
		// Build the link tree elements...
1603
		$context['linktree'][] = array(
1604
			'url' => $scripturl . '?action=pm;sa=manlabels',
1605
			'name' => $txt['pm_manage_labels']
1606
		);
1607
1608
		// Some things for the template
1609
		$context['page_title'] = $txt['pm_manage_labels'];
1610
		$context['sub_template'] = 'labels';
1611
1612
		// Add all existing labels to the array to save, slashing them as necessary...
1613
		$the_labels = array();
1614
		foreach ($context['labels'] as $label)
1615
		{
1616
			if ($label['id'] != -1)
1617
			{
1618
				$the_labels[$label['id']] = $label['name'];
1619
			}
1620
		}
1621
1622
		// Submitting changes?
1623
		if (isset($this->_req->post->add) || isset($this->_req->post->delete) || isset($this->_req->post->save))
1624
		{
1625
			checkSession('post');
1626
1627
			// This will be for updating messages.
1628
			$message_changes = array();
1629
			$new_labels = array();
1630
			$rule_changes = array();
1631
1632
			// Will most likely need this.
1633
			loadRules();
1634
1635
			// Adding a new label?
1636
			if (isset($this->_req->post->add))
1637
			{
1638
				$this->_req->post->label = strtr(Util::htmlspecialchars(trim($this->_req->post->label)), array(',' => '&#044;'));
1639
1640
				if (Util::strlen($this->_req->post->label) > 30)
1641
				{
1642
					$this->_req->post->label = Util::substr($this->_req->post->label, 0, 30);
1643
				}
1644
				if ($this->_req->post->label != '')
1645
				{
1646
					$the_labels[] = $this->_req->post->label;
1647
				}
1648
			}
1649
			// Deleting an existing label?
1650
			elseif (isset($this->_req->post->delete, $this->_req->post->delete_label))
1651
			{
1652
				$i = 0;
1653
				foreach ($the_labels as $id => $name)
1654
				{
1655
					if (isset($this->_req->post->delete_label[$id]))
1656
					{
1657
						unset($the_labels[$id]);
1658
						$message_changes[$id] = true;
1659
					}
1660
					else
1661
					{
1662
						$new_labels[$id] = $i++;
1663
					}
1664
				}
1665
			}
1666
			// The hardest one to deal with... changes.
1667
			elseif (isset($this->_req->post->save) && !empty($this->_req->post->label_name))
1668
			{
1669
				$i = 0;
1670
				foreach ($the_labels as $id => $name)
1671
				{
1672
					if ($id == -1)
1673
					{
1674
						continue;
1675
					}
1676
					elseif (isset($this->_req->post->label_name[$id]))
1677
					{
1678
						// Prepare the label name
1679
						$this->_req->post->label_name[$id] = trim(strtr(Util::htmlspecialchars($this->_req->post->label_name[$id]), array(',' => '&#044;')));
1680
1681
						// Has to fit in the database as well
1682
						if (Util::strlen($this->_req->post->label_name[$id]) > 30)
1683
						{
1684
							$this->_req->post->label_name[$id] = Util::substr($this->_req->post->label_name[$id], 0, 30);
1685
						}
1686
1687
						if ($this->_req->post->label_name[$id] != '')
1688
						{
1689
							$the_labels[(int) $id] = $this->_req->post->label_name[$id];
1690
							$new_labels[$id] = $i++;
1691
						}
1692
						else
1693
						{
1694
							unset($the_labels[(int) $id]);
1695
							$message_changes[(int) $id] = true;
1696
						}
1697
					}
1698
					else
1699
					{
1700
						$new_labels[$id] = $i++;
1701
					}
1702
				}
1703
			}
1704
1705
			// Save the label status.
1706
			require_once(SUBSDIR . '/Members.subs.php');
1707
			updateMemberData($user_info['id'], array('message_labels' => implode(',', $the_labels)));
1708
1709
			// Update all the messages currently with any label changes in them!
1710
			if (!empty($message_changes))
1711
			{
1712
				$searchArray = array_keys($message_changes);
1713
1714
				if (!empty($new_labels))
1715
				{
1716
					for ($i = max($searchArray) + 1, $n = max(array_keys($new_labels)); $i <= $n; $i++)
1717
						$searchArray[] = $i;
1718
				}
1719
1720
				updateLabelsToPM($searchArray, $new_labels, $user_info['id']);
1721
1722
				// Now do the same the rules - check through each rule.
1723
				foreach ($context['rules'] as $k => $rule)
1724
				{
1725
					// Each action...
1726
					foreach ($rule['actions'] as $k2 => $action)
1727
					{
1728
						if ($action['t'] != 'lab' || !in_array($action['v'], $searchArray))
1729
						{
1730
							continue;
1731
						}
1732
1733
						$rule_changes[] = $rule['id'];
1734
1735
						// If we're here we have a label which is either changed or gone...
1736
						if (isset($new_labels[$action['v']]))
1737
						{
1738
							$context['rules'][$k]['actions'][$k2]['v'] = $new_labels[$action['v']];
1739
						}
1740
						else
1741
						{
1742
							unset($context['rules'][$k]['actions'][$k2]);
1743
						}
1744
					}
1745
				}
1746
			}
1747
1748
			// If we have rules to change do so now.
1749
			if (!empty($rule_changes))
1750
			{
1751
				$rule_changes = array_unique($rule_changes);
1752
1753
				// Update/delete as appropriate.
1754
				foreach ($rule_changes as $k => $id)
1755
					if (!empty($context['rules'][$id]['actions']))
1756
					{
1757
						updatePMRuleAction($id, $user_info['id'], $context['rules'][$id]['actions']);
1758
						unset($rule_changes[$k]);
1759
					}
1760
1761
				// Anything left here means it's lost all actions...
1762
				if (!empty($rule_changes))
1763
				{
1764
					deletePMRules($user_info['id'], $rule_changes);
1765
				}
1766
			}
1767
1768
			// Make sure we're not caching this!
1769
			Cache::instance()->remove('labelCounts:' . $user_info['id']);
1770
1771
			// To make the changes appear right away, redirect.
1772
			redirectexit('action=pm;sa=manlabels');
1773
		}
1774
	}
1775
1776
	/**
1777
	 * Allows to edit Personal Message Settings.
1778
	 *
1779
	 * @uses ProfileOptions controller. (@todo refactor this.)
1780
	 * @uses Profile template.
1781
	 * @uses Profile language file.
1782
	 */
1783
	public function action_settings()
1784
	{
1785
		global $txt, $user_info, $context, $scripturl, $profile_vars, $cur_profile, $user_profile;
1786
1787
		require_once(SUBSDIR . '/Profile.subs.php');
1788
1789
		// Load the member data for editing
1790
		loadMemberData($user_info['id'], false, 'profile');
1791
		$cur_profile = $user_profile[$user_info['id']];
1792
1793
		// Load up the profile template, its where PM settings are located
1794
		loadLanguage('Profile');
1795
		loadTemplate('Profile');
1796
1797
		// We want them to submit back to here.
1798
		$context['profile_custom_submit_url'] = $scripturl . '?action=pm;sa=settings;save';
1799
1800
		$context['page_title'] = $txt['pm_settings'];
1801
		$context['user']['is_owner'] = true;
1802
		$context['id_member'] = $user_info['id'];
1803
		$context['require_password'] = false;
1804
		$context['menu_item_selected'] = 'settings';
1805
		$context['submit_button_text'] = $txt['pm_settings'];
1806
1807
		// Add our position to the linktree.
1808
		$context['linktree'][] = array(
1809
			'url' => $scripturl . '?action=pm;sa=settings',
1810
			'name' => $txt['pm_settings']
1811
		);
1812
1813
		// Are they saving?
1814
		if (isset($this->_req->post->save))
1815
		{
1816
			checkSession('post');
1817
1818
			// Mimic what profile would do.
1819
			// @todo fix this when Profile.subs is not dependant on this behavior
1820
			$_POST = htmltrim__recursive((array) $this->_req->post);
1821
			$_POST = htmlspecialchars__recursive($_POST);
1822
1823
			// Save the fields.
1824
			require_once(CONTROLLERDIR . '/ProfileOptions.controller.php');
1825
			$fields = ProfileOptions_Controller::getFields('contactprefs');
1826
			saveProfileFields($fields['fields'], $fields['hook']);
1827
1828
			if (!empty($profile_vars))
1829
			{
1830
				require_once(SUBSDIR . '/Members.subs.php');
1831
				updateMemberData($user_info['id'], $profile_vars);
1832
			}
1833
1834
			// Invalidate any cached data and reload so we show the saved values
1835
			Cache::instance()->remove('member_data-profile-' . $user_info['id']);
1836
			loadMemberData($user_info['id'], false, 'profile');
1837
			$cur_profile = $user_profile[$user_info['id']];
1838
		}
1839
1840
		// Load up the fields.
1841
		$controller = new ProfileOptions_Controller(new Event_Manager());
1842
		$controller->pre_dispatch();
1843
		$controller->action_pmprefs();
1844
	}
1845
1846
	/**
1847
	 * Allows the user to report a personal message to an administrator.
1848
	 *
1849
	 * What it does:
1850
	 *
1851
	 * - In the first instance requires that the ID of the message to report is passed through $_GET.
1852
	 * - It allows the user to report to either a particular administrator - or the whole admin team.
1853
	 * - It will forward on a copy of the original message without allowing the reporter to make changes.
1854
	 *
1855
	 * @uses report_message sub-template.
1856
	 */
1857
	public function action_report()
1858
	{
1859
		global $txt, $context, $user_info, $language, $modSettings;
1860
1861
		// Check that this feature is even enabled!
1862
		if (empty($modSettings['enableReportPM']) || empty($this->_req->query->pmsg))
1863
		{
1864
			throw new Elk_Exception('no_access', false);
1865
		}
1866
1867
		$pmsg = $this->_req->getQuery('pmsg', 'intval', $this->_req->getPost('pmsg', 'intval', 0));
1868
1869
		if (!isAccessiblePM($pmsg, 'inbox'))
1870
		{
1871
			throw new Elk_Exception('no_access', false);
1872
		}
1873
1874
		$context['pm_id'] = $pmsg;
1875
		$context['page_title'] = $txt['pm_report_title'];
1876
		$context['sub_template'] = 'report_message';
1877
1878
		// We'll query some members, we will.
1879
		require_once(SUBSDIR . '/Members.subs.php');
1880
1881
		// If we're here, just send the user to the template, with a few useful context bits.
1882
		if (isset($this->_req->post->report))
1883
		{
1884
			$poster_comment = strtr(Util::htmlspecialchars($this->_req->post->reason), array("\r" => '', "\t" => ''));
1885
1886
			if (Util::strlen($poster_comment) > 254)
1887
			{
1888
				throw new Elk_Exception('post_too_long', false);
1889
			}
1890
1891
			// Check the session before proceeding any further!
1892
			checkSession('post');
1893
1894
			// First, load up the message they want to file a complaint against, and verify it actually went to them!
1895
			list ($subject, $body, $time, $memberFromID, $memberFromName, $poster_name, $time_message) = loadPersonalMessage($pmsg);
1896
1897
			require_once(SUBSDIR . '/Messages.subs.php');
1898
1899
			recordReport(array(
1900
				'id_msg' => $pmsg,
1901
				'id_topic' => 0,
1902
				'id_board' => 0,
1903
				'type' => 'pm',
1904
				'id_poster' => $memberFromID,
1905
				'real_name' => $memberFromName,
1906
				'poster_name' => $poster_name,
1907
				'subject' => $subject,
1908
				'body' => $body,
1909
				'time_message' => $time_message,
1910
			), $poster_comment);
1911
1912
			// Remove the line breaks...
1913
			$body = preg_replace('~<br ?/?' . '>~i', "\n", $body);
1914
1915
			$recipients = array();
1916
			$temp = loadPMRecipientsAll($context['pm_id'], true);
1917
			foreach ($temp as $recipient)
1918
				$recipients[] = $recipient['link'];
1919
1920
			// Now let's get out and loop through the admins.
1921
			$admins = admins(isset($this->_req->post->id_admin) ? (int) $this->_req->post->id_admin : 0);
1922
1923
			// Maybe we shouldn't advertise this?
1924
			if (empty($admins))
1925
			{
1926
				throw new Elk_Exception('no_access', false);
1927
			}
1928
1929
			$memberFromName = un_htmlspecialchars($memberFromName);
1930
1931
			// Prepare the message storage array.
1932
			$messagesToSend = array();
1933
1934
			// Loop through each admin, and add them to the right language pile...
1935
			foreach ($admins as $id_admin => $admin_info)
1936
			{
1937
				// Need to send in the correct language!
1938
				$cur_language = empty($admin_info['lngfile']) || empty($modSettings['userLanguage']) ? $language : $admin_info['lngfile'];
1939
1940
				if (!isset($messagesToSend[$cur_language]))
1941
				{
1942
					loadLanguage('PersonalMessage', $cur_language, false);
1943
1944
					// Make the body.
1945
					$report_body = str_replace(array('{REPORTER}', '{SENDER}'), array(un_htmlspecialchars($user_info['name']), $memberFromName), $txt['pm_report_pm_user_sent']);
1946
					$report_body .= "\n" . '[b]' . $this->_req->post->reason . '[/b]' . "\n\n";
1947
					if (!empty($recipients))
1948
					{
1949
						$report_body .= $txt['pm_report_pm_other_recipients'] . ' ' . implode(', ', $recipients) . "\n\n";
1950
					}
1951
					$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]';
1952
1953
					// Plonk it in the array ;)
1954
					$messagesToSend[$cur_language] = array(
1955
						'subject' => (Util::strpos($subject, $txt['pm_report_pm_subject']) === false ? $txt['pm_report_pm_subject'] : '') . un_htmlspecialchars($subject),
1956
						'body' => $report_body,
1957
						'recipients' => array(
1958
							'to' => array(),
1959
							'bcc' => array()
1960
						),
1961
					);
1962
				}
1963
1964
				// Add them to the list.
1965
				$messagesToSend[$cur_language]['recipients']['to'][$id_admin] = $id_admin;
1966
			}
1967
1968
			// Send a different email for each language.
1969
			foreach ($messagesToSend as $lang => $message)
1970
				sendpm($message['recipients'], $message['subject'], $message['body']);
1971
1972
			// Give the user their own language back!
1973
			if (!empty($modSettings['userLanguage']))
1974
			{
1975
				loadLanguage('PersonalMessage', '', false);
1976
			}
1977
1978
			// Leave them with a template.
1979
			$context['sub_template'] = 'report_message_complete';
1980
		}
1981
	}
1982
1983
	/**
1984
	 * List and allow adding/entering all man rules, such as
1985
	 *
1986
	 * What it does:
1987
	 *
1988
	 * - If it itches, it will be scratched.
1989
	 * - Yes or No are perfectly acceptable answers to almost every question.
1990
	 * - Men see in only 16 colors, Peach, for example, is a fruit, not a color.
1991
	 *
1992
	 * @uses sub template rules
1993
	 */
1994
	public function action_manrules()
1995
	{
1996
		global $txt, $context, $user_info, $scripturl;
1997
1998
		require_once(SUBSDIR . '/PersonalMessage.subs.php');
1999
2000
		// The link tree - gotta have this :o
2001
		$context['linktree'][] = array(
2002
			'url' => $scripturl . '?action=pm;sa=manrules',
2003
			'name' => $txt['pm_manage_rules']
2004
		);
2005
2006
		$context['page_title'] = $txt['pm_manage_rules'];
2007
		$context['sub_template'] = 'rules';
2008
2009
		// Load them... load them!!
2010
		loadRules();
2011
2012
		// Likely to need all the groups!
2013
		require_once(SUBSDIR . '/Membergroups.subs.php');
2014
		$context['groups'] = accessibleGroups();
2015
2016
		// Applying all rules?
2017
		if (isset($this->_req->query->apply))
2018
		{
2019
			checkSession('get');
2020
2021
			applyRules(true);
2022
			redirectexit('action=pm;sa=manrules');
2023
		}
2024
2025
		// Editing a specific rule?
2026
		if (isset($this->_req->query->add))
2027
		{
2028
			$context['rid'] = isset($this->_req->query->rid) && isset($context['rules'][$this->_req->query->rid]) ? (int) $this->_req->query->rid : 0;
2029
			$context['sub_template'] = 'add_rule';
2030
2031
			// Any known rule
2032
			$js_rules = '';
2033
			foreach ($context['known_rules'] as $rule)
2034
			{
2035
				$js_rules[$rule] = $txt['pm_rule_' . $rule];
2036
			}
2037
			$js_rules = json_encode($js_rules);
2038
2039
			// Any known label
2040
			$js_labels = '';
2041
			foreach ($context['labels'] as $label)
2042
			{
2043
				if ($label['id'] != -1)
2044
				{
2045
					$js_labels[$label['id'] + 1] = $label['name'];
2046
				}
2047
			}
2048
			$js_labels = json_encode($js_labels);
2049
2050
			// And all of the groups as well
2051
			$js_groups = json_encode($context['groups']);
2052
2053
			// Oh my, we have a lot of text strings for this
2054
			addJavascriptVar(array(
2055
				'criteriaNum' => 0,
2056
				'actionNum' => 0,
2057
				'groups' => $js_groups,
2058
				'labels' => $js_labels,
2059
				'rules' => $js_rules,
2060
				'txt_pm_readable_and' => $txt['pm_readable_and'],
2061
				'txt_pm_readable_or' => $txt['pm_readable_or'],
2062
				'txt_pm_readable_member' => $txt['pm_readable_member'],
2063
				'txt_pm_readable_group' => $txt['pm_readable_group'],
2064
				'txt_pm_readable_subject ' => $txt['pm_readable_subject'],
2065
				'txt_pm_readable_body' => $txt['pm_readable_body'],
2066
				'txt_pm_readable_buddy' => $txt['pm_readable_buddy'],
2067
				'txt_pm_readable_label' => $txt['pm_readable_label'],
2068
				'txt_pm_readable_delete' => $txt['pm_readable_delete'],
2069
				'txt_pm_readable_start' => $txt['pm_readable_start'],
2070
				'txt_pm_readable_end' => $txt['pm_readable_end'],
2071
				'txt_pm_readable_then' => $txt['pm_readable_then'],
2072
				'txt_pm_rule_not_defined' => $txt['pm_rule_not_defined'],
2073
				'txt_pm_rule_criteria_pick' => $txt['pm_rule_criteria_pick'],
2074
				'txt_pm_rule_sel_group' => $txt['pm_rule_sel_group'],
2075
				'txt_pm_rule_sel_action' => $txt['pm_rule_sel_action'],
2076
				'txt_pm_rule_label' => $txt['pm_rule_label'],
2077
				'txt_pm_rule_delete' => $txt['pm_rule_delete'],
2078
				'txt_pm_rule_sel_label' => $txt['pm_rule_sel_label'],
2079
			), true);
2080
2081
			// Current rule information...
2082
			if ($context['rid'])
2083
			{
2084
				$context['rule'] = $context['rules'][$context['rid']];
2085
				$members = array();
2086
2087
				// Need to get member names!
2088
				foreach ($context['rule']['criteria'] as $k => $criteria)
2089
					if ($criteria['t'] === 'mid' && !empty($criteria['v']))
2090
					{
2091
						$members[(int) $criteria['v']] = $k;
2092
					}
2093
2094
				if (!empty($members))
2095
				{
2096
					require_once(SUBSDIR . '/Members.subs.php');
2097
					$result = getBasicMemberData(array_keys($members));
2098
					foreach ($result as $row)
2099
						$context['rule']['criteria'][$members[$row['id_member']]]['v'] = $row['member_name'];
2100
				}
2101
			}
2102
			else
2103
			{
2104
				$context['rule'] = array(
2105
					'id' => '',
2106
					'name' => '',
2107
					'criteria' => array(),
2108
					'actions' => array(),
2109
					'logic' => 'and',
2110
				);
2111
			}
2112
2113
			// Add a dummy criteria to allow expansion for none js users.
2114
			$context['rule']['criteria'][] = array('t' => '', 'v' => '');
2115
		}
2116
		// Saving?
2117
		elseif (isset($this->_req->query->save))
2118
		{
2119
			checkSession('post');
2120
			$context['rid'] = isset($this->_req->query->rid) && isset($context['rules'][$this->_req->query->rid]) ? (int) $this->_req->query->rid : 0;
2121
2122
			// Name is easy!
2123
			$ruleName = Util::htmlspecialchars(trim($this->_req->post->rule_name));
2124
			if (empty($ruleName))
2125
			{
2126
				throw new Elk_Exception('pm_rule_no_name', false);
2127
			}
2128
2129
			// Sanity check...
2130
			if (empty($this->_req->post->ruletype) || empty($this->_req->post->acttype))
2131
			{
2132
				throw new Elk_Exception('pm_rule_no_criteria', false);
2133
			}
2134
2135
			// Let's do the criteria first - it's also hardest!
2136
			$criteria = array();
2137
			foreach ($this->_req->post->ruletype as $ind => $type)
2138
			{
2139
				// Check everything is here...
2140
				if ($type === 'gid' && (!isset($this->_req->post->ruledefgroup[$ind]) || !isset($context['groups'][$this->_req->post->ruledefgroup[$ind]])))
2141
				{
2142
					continue;
2143
				}
2144
				elseif ($type != 'bud' && !isset($this->_req->post->ruledef[$ind]))
2145
				{
2146
					continue;
2147
				}
2148
2149
				// Members need to be found.
2150
				if ($type === 'mid')
2151
				{
2152
					require_once(SUBSDIR . '/Members.subs.php');
2153
					$name = trim($this->_req->post->ruledef[$ind]);
2154
					$member = getMemberByName($name, true);
2155
					if (empty($member))
2156
					{
2157
						continue;
2158
					}
2159
2160
					$criteria[] = array('t' => 'mid', 'v' => $member['id_member']);
2161
				}
2162
				elseif ($type === 'bud')
2163
				{
2164
					$criteria[] = array('t' => 'bud', 'v' => 1);
2165
				}
2166
				elseif ($type === 'gid')
2167
				{
2168
					$criteria[] = array('t' => 'gid', 'v' => (int) $this->_req->post->ruledefgroup[$ind]);
2169
				}
2170
				elseif (in_array($type, array('sub', 'msg')) && trim($this->_req->post->ruledef[$ind]) != '')
2171
				{
2172
					$criteria[] = array('t' => $type, 'v' => Util::htmlspecialchars(trim($this->_req->post->ruledef[$ind])));
2173
				}
2174
			}
2175
2176
			// Also do the actions!
2177
			$actions = array();
2178
			$doDelete = 0;
2179
			$isOr = $this->_req->post->rule_logic === 'or' ? 1 : 0;
2180
			foreach ($this->_req->post->acttype as $ind => $type)
2181
			{
2182
				// Picking a valid label?
2183
				if ($type === 'lab' && (!isset($this->_req->post->labdef[$ind]) || !isset($context['labels'][$this->_req->post->labdef[$ind] - 1])))
2184
				{
2185
					continue;
2186
				}
2187
2188
				// Record what we're doing.
2189
				if ($type === 'del')
2190
				{
2191
					$doDelete = 1;
2192
				}
2193
				elseif ($type === 'lab')
2194
				{
2195
					$actions[] = array('t' => 'lab', 'v' => (int) $this->_req->post->labdef[$ind] - 1);
2196
				}
2197
			}
2198
2199
			if (empty($criteria) || (empty($actions) && !$doDelete))
2200
			{
2201
				throw new Elk_Exception('pm_rule_no_criteria', false);
2202
			}
2203
2204
			// What are we storing?
2205
			$criteria = serialize($criteria);
2206
			$actions = serialize($actions);
2207
2208
			// Create the rule?
2209
			if (empty($context['rid']))
2210
			{
2211
				addPMRule($user_info['id'], $ruleName, $criteria, $actions, $doDelete, $isOr);
2212
			}
2213
			else
2214
			{
2215
				updatePMRule($user_info['id'], $context['rid'], $ruleName, $criteria, $actions, $doDelete, $isOr);
2216
			}
2217
2218
			redirectexit('action=pm;sa=manrules');
2219
		}
2220
		// Deleting?
2221
		elseif (isset($this->_req->post->delselected) && !empty($this->_req->post->delrule))
2222
		{
2223
			checkSession('post');
2224
			$toDelete = array();
2225
			foreach ($this->_req->post->delrule as $k => $v)
2226
				$toDelete[] = (int) $k;
2227
2228
			if (!empty($toDelete))
2229
			{
2230
				deletePMRules($user_info['id'], $toDelete);
2231
			}
2232
2233
			redirectexit('action=pm;sa=manrules');
2234
		}
2235
	}
2236
2237
	/**
2238
	 * Actually do the search of personal messages and show the results
2239
	 *
2240
	 * What it does:
2241
	 *
2242
	 * - accessed with ?action=pm;sa=search2
2243
	 * - checks user input and searches the pm table for messages matching the query.
2244
	 * - uses the search_results sub template of the PersonalMessage template.
2245
	 * - show the results of the search query.
2246
	 */
2247
	public function action_search2()
2248
	{
2249
		global $scripturl, $modSettings, $context, $txt, $memberContext;
2250
2251
		// Make sure the server is able to do this right now
2252
		if (!empty($modSettings['loadavg_search']) && $modSettings['current_load'] >= $modSettings['loadavg_search'])
2253
		{
2254
			throw new Elk_Exception('loadavg_search_disabled', false);
2255
		}
2256
2257
		// Some useful general permissions.
2258
		$context['can_send_pm'] = allowedTo('pm_send');
2259
2260
		// Extract all the search parameters if coming in from pagination, etc
2261
		$this->_searchParamsFromString();
2262
2263
		// Set a start for pagination
2264
		$context['start'] = $this->_req->getQuery('start', 'intval', 0);
2265
2266
		// Set/clean search criteria
2267
		$this->_prepareSearchParams();
2268
2269
		$context['folder'] = empty($this->_search_params['sent_only']) ? 'inbox' : 'sent';
2270
2271
		// Searching for specific members
2272
		$userQuery = $this->_setUserQuery();
2273
2274
		// Setup the sorting variables...
2275
		$this->_setSortParams();
2276
2277
		// Sort out any labels we may be searching by.
2278
		$labelQuery = $this->_setLabelQuery();
2279
2280
		// Unfortunately, searching for words like this is going to be slow, so we're blacklisting them.
2281
		$blacklisted_words = array('quote', 'the', 'is', 'it', 'are', 'if');
2282
2283
		// What are we actually searching for?
2284
		$this->_search_params['search'] = !empty($this->_search_params['search']) ? $this->_search_params['search'] : (isset($this->_req->post->search) ? $this->_req->post->search : '');
2285
2286
		// If nothing is left to search on - we set an error!
2287
		if (!isset($this->_search_params['search']) || $this->_search_params['search'] === '')
2288
		{
2289
			$context['search_errors']['invalid_search_string'] = true;
2290
		}
2291
2292
		// Change non-word characters into spaces.
2293
		$stripped_query = preg_replace('~(?:[\x0B\0\x{A0}\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~u', ' ', $this->_search_params['search']);
2294
2295
		// Make the query lower case since it will case insensitive anyway.
2296
		$stripped_query = un_htmlspecialchars(Util::strtolower($stripped_query));
2297
2298
		// Extract phrase parts first (e.g. some words "this is a phrase" some more words.)
2299
		preg_match_all('/(?:^|\s)([-]?)"([^"]+)"(?:$|\s)/', $stripped_query, $matches, PREG_PATTERN_ORDER);
2300
		$phraseArray = $matches[2];
2301
2302
		// Remove the phrase parts and extract the words.
2303
		$wordArray = preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~u', ' ', $this->_search_params['search']);
2304
		$wordArray = explode(' ', Util::htmlspecialchars(un_htmlspecialchars($wordArray), ENT_QUOTES));
2305
2306
		// A minus sign in front of a word excludes the word.... so...
2307
		$excludedWords = array();
2308
2309
		// Check for things like -"some words", but not "-some words".
2310
		foreach ($matches[1] as $index => $word)
2311
		{
2312
			if ($word === '-')
2313
			{
2314
				if (($word = trim($phraseArray[$index], '-_\' ')) !== '' && !in_array($word, $blacklisted_words))
2315
				{
2316
					$excludedWords[] = $word;
2317
				}
2318
				unset($phraseArray[$index]);
2319
			}
2320
		}
2321
2322
		// Now we look for -test, etc
2323
		foreach ($wordArray as $index => $word)
2324
		{
2325
			if (strpos(trim($word), '-') === 0)
2326
			{
2327
				if (($word = trim($word, '-_\' ')) !== '' && !in_array($word, $blacklisted_words))
2328
				{
2329
					$excludedWords[] = $word;
2330
				}
2331
				unset($wordArray[$index]);
2332
			}
2333
		}
2334
2335
		// The remaining words and phrases are all included.
2336
		$searchArray = array_merge($phraseArray, $wordArray);
2337
2338
		// Trim everything and make sure there are no words that are the same.
2339
		foreach ($searchArray as $index => $value)
2340
		{
2341
			// Skip anything that's close to empty.
2342
			if (($searchArray[$index] = trim($value, '-_\' ')) === '')
2343
			{
2344
				unset($searchArray[$index]);
2345
			}
2346
			// Skip blacklisted words. Make sure to note we skipped them as well
2347
			elseif (in_array($searchArray[$index], $blacklisted_words))
2348
			{
2349
				$foundBlackListedWords = true;
2350
				unset($searchArray[$index]);
2351
2352
			}
2353
2354
			if (isset($searchArray[$index]))
2355
			{
2356
				$searchArray[$index] = Util::strtolower(trim($value));
2357
2358
				if ($searchArray[$index] === '')
2359
				{
2360
					unset($searchArray[$index]);
2361
				}
2362
				else
2363
				{
2364
					// Sort out entities first.
2365
					$searchArray[$index] = Util::htmlspecialchars($searchArray[$index]);
2366
				}
2367
			}
2368
		}
2369
2370
		$searchArray = array_slice(array_unique($searchArray), 0, 10);
2371
2372
		// This contains *everything*
2373
		$searchWords = array_merge($searchArray, $excludedWords);
2374
2375
		// Make sure at least one word is being searched for.
2376
		if (empty($searchArray))
2377
		{
2378
			$context['search_errors']['invalid_search_string' . (!empty($foundBlackListedWords) ? '_blacklist' : '')] = true;
2379
		}
2380
2381
		// Sort out the search query so the user can edit it - if they want.
2382
		$context['search_params'] = $this->_search_params;
2383
		if (isset($context['search_params']['search']))
2384
		{
2385
			$context['search_params']['search'] = Util::htmlspecialchars($context['search_params']['search']);
2386
		}
2387
2388
		if (isset($context['search_params']['userspec']))
2389
		{
2390
			$context['search_params']['userspec'] = Util::htmlspecialchars($context['search_params']['userspec']);
2391
		}
2392
2393
		// Now we have all the parameters, combine them together for pagination and the like...
2394
		$context['params'] = $this->_compileURLparams();
2395
2396
		// Compile the subject query part.
2397
		$andQueryParts = array();
2398
		foreach ($searchWords as $index => $word)
2399
		{
2400
			if ($word === '')
2401
			{
2402
				continue;
2403
			}
2404
2405
			if ($this->_search_params['subject_only'])
2406
			{
2407
				$andQueryParts[] = 'pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '}';
2408
			}
2409
			else
2410
			{
2411
				$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 . '})';
2412
			}
2413
2414
			$this->_searchq_parameters ['search_' . $index] = '%' . strtr($word, array('_' => '\\_', '%' => '\\%')) . '%';
2415
		}
2416
2417
		$searchQuery = ' 1=1';
2418
		if (!empty($andQueryParts))
2419
		{
2420
			$searchQuery = implode(!empty($this->_search_params['searchtype']) && $this->_search_params['searchtype'] == 2 ? ' OR ' : ' AND ', $andQueryParts);
2421
		}
2422
2423
		// Age limits?
2424
		$timeQuery = '';
2425
		if (!empty($this->_search_params['minage']))
2426
		{
2427
			$timeQuery .= ' AND pm.msgtime < ' . (time() - $this->_search_params['minage'] * 86400);
2428
		}
2429
2430
		if (!empty($this->_search_params['maxage']))
2431
		{
2432
			$timeQuery .= ' AND pm.msgtime > ' . (time() - $this->_search_params['maxage'] * 86400);
2433
		}
2434
2435
		// If we have errors - return back to the first screen...
2436
		if (!empty($context['search_errors']))
2437
		{
2438
			$this->_req->post->params = $context['params'];
2439
2440
			$this->action_search();
2441
			return false;
2442
		}
2443
2444
		// Get the number of results.
2445
		$numResults = numPMSeachResults($userQuery, $labelQuery, $timeQuery, $searchQuery, $this->_searchq_parameters);
2446
2447
		// Get all the matching message ids, senders and head pm nodes
2448
		list($foundMessages, $posters, $head_pms) = loadPMSearchMessages($userQuery, $labelQuery, $timeQuery, $searchQuery, $this->_searchq_parameters, $this->_search_params);
2449
2450
		// Find the real head pm when in conversation view
2451
		if ($context['display_mode'] == 2 && !empty($head_pms))
2452
		{
2453
			$real_pm_ids = loadPMSearchHeads($head_pms);
2454
		}
2455
2456
		// Load the found user data
2457
		$posters = array_unique($posters);
2458
		if (!empty($posters))
2459
		{
2460
			loadMemberData($posters);
2461
		}
2462
2463
		// Sort out the page index.
2464
		$context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=search2;params=' . $context['params'], $this->_req->query->start, $numResults, $modSettings['search_results_per_page'], false);
2465
2466
		$context['message_labels'] = array();
2467
		$context['message_replied'] = array();
2468
		$context['personal_messages'] = array();
2469
		$context['first_label'] = array();
2470
2471
		// If we have results, we have work to do!
2472
		if (!empty($foundMessages))
2473
		{
2474
			$recipients = array();
2475
			list($context['message_labels'], $context['message_replied'], $context['message_unread'], $context['first_label']) = loadPMRecipientInfo($foundMessages, $recipients, $context['folder'], true);
2476
2477
			// Prepare for the callback!
2478
			$search_results = loadPMSearchResults($foundMessages, $this->_search_params);
2479
			$counter = 0;
2480
			$bbc_parser = \BBC\ParserWrapper::instance();
2481
			foreach ($search_results as $row)
2482
			{
2483
				// If there's no subject, use the default.
2484
				$row['subject'] = $row['subject'] === '' ? $txt['no_subject'] : $row['subject'];
2485
2486
				// Load this posters context info, if its not there then fill in the essentials...
2487
				if (!loadMemberContext($row['id_member_from'], true))
2488
				{
2489
					$memberContext[$row['id_member_from']]['name'] = $row['from_name'];
2490
					$memberContext[$row['id_member_from']]['id'] = 0;
2491
					$memberContext[$row['id_member_from']]['group'] = $txt['guest_title'];
2492
					$memberContext[$row['id_member_from']]['link'] = $row['from_name'];
2493
					$memberContext[$row['id_member_from']]['email'] = '';
2494
					$memberContext[$row['id_member_from']]['show_email'] = showEmailAddress(true, 0);
2495
					$memberContext[$row['id_member_from']]['is_guest'] = true;
2496
				}
2497
2498
				// Censor anything we don't want to see...
2499
				$row['body'] = censor($row['body']);
2500
				$row['subject'] = censor($row['subject']);
2501
2502
				// Parse out any BBC...
2503
				$row['body'] = $bbc_parser->parsePM($row['body']);
2504
2505
				// Highlight the hits
2506
				$body_highlighted = '';
2507
				$subject_highlighted = '';
2508
				foreach ($searchArray as $query)
2509
				{
2510
					// Fix the international characters in the keyword too.
2511
					$query = un_htmlspecialchars($query);
2512
					$query = trim($query, '\*+');
2513
					$query = strtr(Util::htmlspecialchars($query), array('\\\'' => '\''));
2514
2515
					$body_highlighted = preg_replace_callback('/((<[^>]*)|' . preg_quote(strtr($query, array('\'' => '&#039;')), '/') . ')/iu', array($this, '_highlighted_callback'), $row['body']);
2516
					$subject_highlighted = preg_replace('/(' . preg_quote($query, '/') . ')/iu', '<strong class="highlight">$1</strong>', $row['subject']);
2517
				}
2518
2519
				// Set a link using the first label information
2520
				$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']]]) && $context['folder'] === 'inbox' ? $real_pm_ids[$head_pms[$row['id_pm']]] : $row['id_pm']) . '#msg_' . $row['id_pm'];
2521
2522
				$context['personal_messages'][] = array(
2523
					'id' => $row['id_pm'],
2524
					'member' => &$memberContext[$row['id_member_from']],
2525
					'subject' => $subject_highlighted,
2526
					'body' => $body_highlighted,
2527
					'time' => standardTime($row['msgtime']),
2528
					'html_time' => htmlTime($row['msgtime']),
2529
					'timestamp' => forum_time(true, $row['msgtime']),
2530
					'recipients' => &$recipients[$row['id_pm']],
2531
					'labels' => &$context['message_labels'][$row['id_pm']],
2532
					'fully_labeled' => count($context['message_labels'][$row['id_pm']]) == count($context['labels']),
2533
					'is_replied_to' => &$context['message_replied'][$row['id_pm']],
2534
					'href' => $href,
2535
					'link' => '<a href="' . $href . '">' . $subject_highlighted . '</a>',
2536
					'counter' => ++$counter,
2537
				);
2538
			}
2539
		}
2540
2541
		// Finish off the context.
2542
		$context['page_title'] = $txt['pm_search_title'];
2543
		$context['sub_template'] = 'search_results';
2544
		$context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'search';
2545
		$context['linktree'][] = array(
2546
			'url' => $scripturl . '?action=pm;sa=search',
2547
			'name' => $txt['pm_search_bar_title'],
2548
		);
2549
	}
2550
2551
	/**
2552
	 * Read / Set the sort parameters for the results listing
2553
	 */
2554
	private function _setSortParams()
2555
	{
2556
		$sort_columns = array(
2557
			'pm.id_pm',
2558
		);
2559
2560
		if (empty($this->_search_params['sort']) && !empty($this->_req->post->sort))
2561
		{
2562
			list ($this->_search_params['sort'], $this->_search_params['sort_dir']) = array_pad(explode('|', $this->_req->post->sort), 2, '');
2563
		}
2564
2565
		$this->_search_params['sort'] = !empty($this->_search_params['sort']) && in_array($this->_search_params['sort'], $sort_columns) ? $this->_search_params['sort'] : 'pm.id_pm';
2566
		$this->_search_params['sort_dir'] = !empty($this->_search_params['sort_dir']) && $this->_search_params['sort_dir'] === 'asc' ? 'asc' : 'desc';
2567
	}
2568
2569
	/**
2570
	 * Handles the parameters when searching on specific labels
2571
	 *
2572
	 * What it does:
2573
	 *
2574
	 * - Returns the label query for use in the main search query
2575
	 * - Sets the parameters for use in the query
2576
	 *
2577
	 * @return string
2578
	 */
2579
	private function _setLabelQuery()
2580
	{
2581
		global $context;
2582
2583
		$db = database();
2584
2585
		$labelQuery = '';
2586
2587
		if ($context['folder'] === 'inbox' && !empty($this->_search_params['advanced']) && $context['currently_using_labels'])
2588
		{
2589
			// Came here from pagination?  Put them back into $_REQUEST for sanitation.
2590
			if (isset($this->_search_params['labels']))
2591
			{
2592
				$this->_req->post->searchlabel = explode(',', $this->_search_params['labels']);
2593
			}
2594
2595
			// Assuming we have some labels - make them all integers.
2596
			if (!empty($this->_req->post->searchlabel) && is_array($this->_req->post->searchlabel))
2597
			{
2598
				$this->_req->post->searchlabel = array_map('intval', $this->_req->post->searchlabel);
2599
			}
2600
			else
2601
			{
2602
				$this->_req->post->searchlabel = array();
2603
			}
2604
2605
			// Now that everything is cleaned up a bit, make the labels a param.
2606
			$this->_search_params['labels'] = implode(',', $this->_req->post->searchlabel);
2607
2608
			// No labels selected? That must be an error!
2609
			if (empty($this->_req->post->searchlabel))
2610
			{
2611
				$context['search_errors']['no_labels_selected'] = true;
2612
			}
2613
			// Otherwise prepare the query!
2614
			elseif (count($this->_req->post->searchlabel) != count($context['labels']))
2615
			{
2616
				$labelQuery = '
2617
				AND {raw:label_implode}';
2618
2619
				$labelStatements = array();
2620
				foreach ($this->_req->post->searchlabel as $label)
2621
					$labelStatements[] = $db->quote('FIND_IN_SET({string:label}, pmr.labels) != 0', array('label' => $label,));
2622
2623
				$this->_searchq_parameters ['label_implode'] = '(' . implode(' OR ', $labelStatements) . ')';
2624
			}
2625
		}
2626
2627
		return $labelQuery;
2628
	}
2629
2630
	/**
2631
	 * Handles the parameters when searching on specific users
2632
	 *
2633
	 * What it does:
2634
	 *
2635
	 * - Returns the user query for use in the main search query
2636
	 * - Sets the parameters for use in the query
2637
	 *
2638
	 * @return string
2639
	 */
2640
	private function _setUserQuery()
2641
	{
2642
		global $context;
2643
2644
		// Hardcoded variables that can be tweaked if required.
2645
		$maxMembersToSearch = 500;
2646
2647
		// Init to not be searching based on members
2648
		$userQuery = '';
2649
2650
		// If there's no specific user, then don't mention it in the main query.
2651
		if (!empty($this->_search_params['userspec']))
2652
		{
2653
			// Set up so we can search by user name, wildcards, like, etc
2654
			$userString = strtr(Util::htmlspecialchars($this->_search_params['userspec'], ENT_QUOTES), array('&quot;' => '"'));
2655
			$userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_'));
2656
2657
			preg_match_all('~"([^"]+)"~', $userString, $matches);
2658
			$possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString)));
2659
2660
			// Who matches those criteria?
2661
			require_once(SUBSDIR . '/Members.subs.php');
2662
			$members = membersBy('member_names', array('member_names' => $possible_users));
2663
2664
			foreach ($possible_users as $key => $possible_user)
2665
				$this->_searchq_parameters ['guest_user_name_implode_' . $key] = defined('DB_CASE_SENSITIVE') ? strtolower($possible_user) : $possible_user;
2666
2667
			// Simply do nothing if there are too many members matching the criteria.
2668
			if (count($members) > $maxMembersToSearch)
2669
			{
2670
				$userQuery = '';
2671
			}
2672
			elseif (count($members) == 0)
2673
			{
2674
				if ($context['folder'] === 'inbox')
2675
				{
2676
					$uq = array();
2677
					$name = defined('DB_CASE_SENSITIVE') ? 'LOWER(pm.from_name)' : 'pm.from_name';
2678
					foreach (array_keys($possible_users) as $key)
2679
						$uq[] = 'AND pm.id_member_from = 0 AND (' . $name . ' LIKE {string:guest_user_name_implode_' . $key . '})';
2680
					$userQuery = implode(' ', $uq);
2681
					$this->_searchq_parameters ['pm_from_name'] = defined('DB_CASE_SENSITIVE') ? 'LOWER(pm.from_name)' : 'pm.from_name';
2682
				}
2683
				else
2684
				{
2685
					$userQuery = '';
2686
				}
2687
			}
2688
			else
2689
			{
2690
				$memberlist = array();
2691
				foreach ($members as $id)
2692
					$memberlist[] = $id;
2693
2694
				// Use the name as as sent from or sent to
2695
				if ($context['folder'] === 'inbox')
2696
				{
2697
					$uq = array();
2698
					$name = defined('DB_CASE_SENSITIVE') ? 'LOWER(pm.from_name)' : 'pm.from_name';
2699
2700
					foreach (array_keys($possible_users) as $key)
2701
						$uq[] = 'AND (pm.id_member_from IN ({array_int:member_list}) OR (pm.id_member_from = 0 AND (' . $name . ' LIKE {string:guest_user_name_implode_' . $key . '})))';
2702
2703
					$userQuery = implode(' ', $uq);
2704
				}
2705
				else
2706
				{
2707
					$userQuery = 'AND (pmr.id_member IN ({array_int:member_list}))';
2708
				}
2709
2710
				$this->_searchq_parameters ['pm_from_name'] = defined('DB_CASE_SENSITIVE') ? 'LOWER(pm.from_name)' : 'pm.from_name';
2711
				$this->_searchq_parameters ['member_list'] = $memberlist;
2712
			}
2713
		}
2714
2715
		return $userQuery;
2716
	}
2717
2718
	/**
2719
	 * Sets the search params for the query
2720
	 *
2721
	 * What it does:
2722
	 *
2723
	 * - Uses existing ones if coming from pagination or uses those passed from the search pm form
2724
	 * - Validates passed params are valid
2725
	 */
2726
	private function _prepareSearchParams()
2727
	{
2728
		// Store whether simple search was used (needed if the user wants to do another query).
2729
		if (!isset($this->_search_params['advanced']))
2730
		{
2731
			$this->_search_params['advanced'] = empty($this->_req->post->advanced) ? 0 : 1;
2732
		}
2733
2734
		// 1 => 'allwords' (default, don't set as param),  2 => 'anywords'.
2735
		if (!empty($this->_search_params['searchtype']) || (!empty($this->_req->post->searchtype) && $this->_req->post->searchtype == 2))
2736
		{
2737
			$this->_search_params['searchtype'] = 2;
2738
		}
2739
2740
		// Minimum age of messages. Default to zero (don't set param in that case).
2741
		if (!empty($this->_search_params['minage']) || (!empty($this->_req->post->minage) && $this->_req->post->minage > 0))
2742
		{
2743
			$this->_search_params['minage'] = !empty($this->_search_params['minage']) ? (int) $this->_search_params['minage'] : (int) $this->_req->post->minage;
2744
		}
2745
2746
		// Maximum age of messages. Default to infinite (9999 days: param not set).
2747
		if (!empty($this->_search_params['maxage']) || (!empty($this->_req->post->maxage) && $this->_req->post->maxage < 9999))
2748
		{
2749
			$this->_search_params['maxage'] = !empty($this->_search_params['maxage']) ? (int) $this->_search_params['maxage'] : (int) $this->_req->post->maxage;
2750
		}
2751
2752
		// Default the user name to a wildcard matching every user (*).
2753
		if (!empty($this->_search_params['userspec']) || (!empty($this->_req->post->userspec) && $this->_req->post->userspec != '*'))
2754
		{
2755
			$this->_search_params['userspec'] = isset($this->_search_params['userspec']) ? $this->_search_params['userspec'] : $this->_req->post->userspec;
2756
		}
2757
2758
		// Search modifiers
2759
		$this->_search_params['subject_only'] = !empty($this->_search_params['subject_only']) || !empty($this->_req->post->subject_only);
2760
		$this->_search_params['show_complete'] = !empty($this->_search_params['show_complete']) || !empty($this->_req->post->show_complete);
2761
		$this->_search_params['sent_only'] = !empty($this->_search_params['sent_only']) || !empty($this->_req->post->sent_only);
2762
	}
2763
2764
	/**
2765
	 * Extract search params from a string
2766
	 *
2767
	 * What it does:
2768
	 *
2769
	 * - When paging search results, reads and decodes the passed parameters
2770
	 * - Places what it finds back in search_params
2771
	 */
2772
	private function _searchParamsFromString()
2773
	{
2774
		$this->_search_params = array();
2775
2776
		if (isset($this->_req->query->params) || isset($this->_req->post->params))
2777
		{
2778
			// Feed it
2779
			$temp_params = isset($this->_req->query->params) ? $this->_req->query->params : $this->_req->post->params;
2780
2781
			// Decode and replace the uri safe characters we added
2782
			$temp_params = base64_decode(str_replace(array('-', '_', '.'), array('+', '/', '='), $temp_params));
2783
2784
			$temp_params = explode('|"|', $temp_params);
2785
			foreach ($temp_params as $i => $data)
2786
			{
2787
				list ($k, $v) = array_pad(explode('|\'|', $data), 2, '');
2788
				$this->_search_params[$k] = $v;
2789
			}
2790
		}
2791
2792
		return $this->_search_params;
2793
	}
2794
2795
	/**
2796
	 * Encodes search params in an URL-compatible way
2797
	 *
2798
	 * @return string - the encoded string to be appended to the URL
2799
	 */
2800
	private function _compileURLparams()
2801
	{
2802
		$encoded = array();
2803
2804
		// Now we have all the parameters, combine them together for pagination and the like...
2805
		foreach ($this->_search_params as $k => $v)
2806
			$encoded[] = $k . '|\'|' . $v;
2807
2808
		// Base64 encode, then replace +/= with uri safe ones that can be reverted
2809
		$encoded = str_replace(array('+', '/', '='), array('-', '_', '.'), base64_encode(implode('|"|', $encoded)));
2810
2811
		return $encoded;
2812
	}
2813
2814
	/**
2815
	 * Allows to search through personal messages.
2816
	 *
2817
	 * What it does:
2818
	 *
2819
	 * - accessed with ?action=pm;sa=search
2820
	 * - shows the screen to search pm's (?action=pm;sa=search)
2821
	 * - uses the search sub template of the PersonalMessage template.
2822
	 * - decodes and loads search parameters given in the URL (if any).
2823
	 * - the form redirects to index.php?action=pm;sa=search2.
2824
	 *
2825
	 * @uses search sub template
2826
	 */
2827
	public function action_search()
2828
	{
2829
		global $context, $txt, $scripturl;
2830
2831
		// If they provided some search parameters, we need to extract them
2832
		if (isset($this->_req->post->params))
2833
		{
2834
			$context['search_params'] = $this->_searchParamsFromString();
2835
		}
2836
2837
		// Set up the search criteria, type, what, age, etc
2838
		if (isset($this->_req->post->search))
2839
		{
2840
			$context['search_params']['search'] = un_htmlspecialchars($this->_req->post->search);
2841
			$context['search_params']['search'] = htmlspecialchars($context['search_params']['search'], ENT_COMPAT, 'UTF-8');
2842
		}
2843
2844
		if (isset($context['search_params']['userspec']))
2845
		{
2846
			$context['search_params']['userspec'] = htmlspecialchars($context['search_params']['userspec'], ENT_COMPAT, 'UTF-8');
2847
		}
2848
2849
		// 1 => 'allwords' / 2 => 'anywords'.
2850
		if (!empty($context['search_params']['searchtype']))
2851
		{
2852
			$context['search_params']['searchtype'] = 2;
2853
		}
2854
2855
		// Minimum and Maximum age of the message
2856
		if (!empty($context['search_params']['minage']))
2857
		{
2858
			$context['search_params']['minage'] = (int) $context['search_params']['minage'];
2859
		}
2860
		if (!empty($context['search_params']['maxage']))
2861
		{
2862
			$context['search_params']['maxage'] = (int) $context['search_params']['maxage'];
2863
		}
2864
2865
		$context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']);
2866
		$context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']);
2867
2868
		// Create the array of labels to be searched.
2869
		$context['search_labels'] = array();
2870
		$searchedLabels = isset($context['search_params']['labels']) && $context['search_params']['labels'] != '' ? explode(',', $context['search_params']['labels']) : array();
2871
		foreach ($context['labels'] as $label)
2872
		{
2873
			$context['search_labels'][] = array(
2874
				'id' => $label['id'],
2875
				'name' => $label['name'],
2876
				'checked' => !empty($searchedLabels) ? in_array($label['id'], $searchedLabels) : true,
2877
			);
2878
		}
2879
2880
		// Are all the labels checked?
2881
		$context['check_all'] = empty($searchedLabels) || count($context['search_labels']) == count($searchedLabels);
2882
2883
		// Load the error text strings if there were errors in the search.
2884
		if (!empty($context['search_errors']))
2885
		{
2886
			loadLanguage('Errors');
2887
			$context['search_errors']['messages'] = array();
2888
			foreach ($context['search_errors'] as $search_error => $dummy)
2889
			{
2890
				if ($search_error === 'messages')
2891
				{
2892
					continue;
2893
				}
2894
2895
				$context['search_errors']['messages'][] = $txt['error_' . $search_error];
2896
			}
2897
		}
2898
2899
		$context['page_title'] = $txt['pm_search_title'];
2900
		$context['sub_template'] = 'search';
2901
		$context['linktree'][] = array(
2902
			'url' => $scripturl . '?action=pm;sa=search',
2903
			'name' => $txt['pm_search_bar_title'],
2904
		);
2905
	}
2906
2907
	/**
2908
	 * Allows the user to mark a personal message as unread so they remember to come back to it
2909
	 */
2910
	public function action_markunread()
2911
	{
2912
		global $context;
2913
2914
		checkSession('request');
2915
2916
		$pmsg = !empty($this->_req->query->pmsg) ? (int) $this->_req->query->pmsg : null;
2917
2918
		// Marking a message as unread, we need a message that was sent to them
2919
		// Can't mark your own reply as unread, that would be weird
2920
		if (!is_null($pmsg) && checkPMReceived($pmsg))
2921
		{
2922
			// Make sure this is accessible, should be of course
2923
			if (!isAccessiblePM($pmsg, 'inbox'))
2924
			{
2925
				throw new Elk_Exception('no_access', false);
2926
			}
2927
2928
			// Well then, you get to hear about it all over again
2929
			markMessagesUnread($pmsg);
2930
		}
2931
2932
		// Back to the folder.
2933
		redirectexit($context['current_label_redirect']);
2934
	}
2935
2936
	/**
2937
	 * Used to highlight body text with strings that match the search term
2938
	 *
2939
	 * - Callback function used in $body_highlighted
2940
	 *
2941
	 * @param string[] $matches
2942
	 */
2943
	private function _highlighted_callback($matches)
2944
	{
2945
		return isset($matches[2]) && $matches[2] == $matches[1] ? stripslashes($matches[1]) : '<strong class="highlight">' . $matches[1] . '</strong>';
2946
	}
2947
}
2948
2949
/**
2950
 * Get a personal message for the template. (used to save memory.)
2951
 *
2952
 * - This is a callback function that will fetch the actual results, as needed, of a previously run
2953
 * subject (loadPMSubjectRequest) or message (loadPMMessageRequest) query.
2954
 *
2955
 * @param string $type
2956
 * @param boolean $reset
2957
 */
2958
function preparePMContext_callback($type = 'subject', $reset = false)
2959
{
2960
	global $txt, $scripturl, $modSettings, $settings, $context, $memberContext, $recipients, $user_info;
2961
	global $subjects_request, $messages_request;
2962
	static $counter = null, $temp_pm_selected = null;
2963
2964
	// We need this
2965
	$db = database();
2966
2967
	// Count the current message number....
2968
	if ($counter === null || $reset)
2969
	{
2970
		$counter = $context['start'];
2971
	}
2972
2973
	if ($temp_pm_selected === null)
2974
	{
2975
		$temp_pm_selected = isset($_SESSION['pm_selected']) ? $_SESSION['pm_selected'] : array();
2976
		$_SESSION['pm_selected'] = array();
2977
	}
2978
2979
	// If we're in non-boring view do something exciting!
2980
	if ($context['display_mode'] != 0 && $subjects_request && $type === 'subject')
2981
	{
2982
		$subject = $db->fetch_assoc($subjects_request);
2983
		if (!$subject)
2984
		{
2985
			$db->free_result($subjects_request);
2986
2987
			return false;
2988
		}
2989
2990
		// Make sure we have a subject
2991
		$subject['subject'] = $subject['subject'] === '' ? $txt['no_subject'] : $subject['subject'];
2992
		$subject['subject'] = censor($subject['subject']);
2993
2994
		$output = array(
2995
			'id' => $subject['id_pm'],
2996
			'member' => array(
2997
				'id' => $subject['id_member_from'],
2998
				'name' => $subject['from_name'],
2999
				'link' => $subject['not_guest'] ? '<a href="' . $scripturl . '?action=profile;u=' . $subject['id_member_from'] . '">' . $subject['from_name'] . '</a>' : $subject['from_name'],
3000
			),
3001
			'recipients' => &$recipients[$subject['id_pm']],
3002
			'subject' => $subject['subject'],
3003
			'time' => standardTime($subject['msgtime']),
3004
			'html_time' => htmlTime($subject['msgtime']),
3005
			'timestamp' => forum_time(true, $subject['msgtime']),
3006
			'number_recipients' => count($recipients[$subject['id_pm']]['to']),
3007
			'labels' => &$context['message_labels'][$subject['id_pm']],
3008
			'fully_labeled' => count($context['message_labels'][$subject['id_pm']]) == count($context['labels']),
3009
			'is_replied_to' => &$context['message_replied'][$subject['id_pm']],
3010
			'is_unread' => &$context['message_unread'][$subject['id_pm']],
3011
			'is_selected' => !empty($temp_pm_selected) && in_array($subject['id_pm'], $temp_pm_selected),
3012
		);
3013
3014
		// In conversation view we need to indicate on the subject listing if any message inside of
3015
		// that conversation is unread, not just if the latest is unread.
3016
		if ($context['display_mode'] == 2 && isset($context['conversation_unread'][$output['id']]))
3017
		{
3018
			$output['is_unread'] = true;
3019
		}
3020
3021
		return $output;
3022
	}
3023
3024
	// Bail if it's false, ie. no messages.
3025
	if ($messages_request === false)
3026
	{
3027
		return false;
3028
	}
3029
3030
	// Reset the data?
3031
	if ($reset === true)
3032
	{
3033
		return $db->data_seek($messages_request, 0);
3034
	}
3035
3036
	// Get the next one... bail if anything goes wrong.
3037
	$message = $db->fetch_assoc($messages_request);
3038
	if (!$message)
3039
	{
3040
		if ($type != 'subject')
3041
		{
3042
			$db->free_result($messages_request);
3043
		}
3044
3045
		return false;
3046
	}
3047
3048
	// Use '(no subject)' if none was specified.
3049
	$message['subject'] = $message['subject'] === '' ? $txt['no_subject'] : $message['subject'];
3050
3051
	// Load the message's information - if it's not there, load the guest information.
3052
	if (!loadMemberContext($message['id_member_from'], true))
3053
	{
3054
		$memberContext[$message['id_member_from']]['name'] = $message['from_name'];
3055
		$memberContext[$message['id_member_from']]['id'] = 0;
3056
3057
		// Sometimes the forum sends messages itself (Warnings are an example) - in this case don't label it from a guest.
3058
		$memberContext[$message['id_member_from']]['group'] = $message['from_name'] == $context['forum_name'] ? '' : $txt['guest_title'];
3059
		$memberContext[$message['id_member_from']]['link'] = $message['from_name'];
3060
		$memberContext[$message['id_member_from']]['email'] = '';
3061
		$memberContext[$message['id_member_from']]['show_email'] = showEmailAddress(true, 0);
3062
		$memberContext[$message['id_member_from']]['is_guest'] = true;
3063
	}
3064
	else
3065
	{
3066
		$memberContext[$message['id_member_from']]['can_view_profile'] = allowedTo('profile_view_any') || ($message['id_member_from'] == $user_info['id'] && allowedTo('profile_view_own'));
3067
		$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'])));
3068
	}
3069
3070
	$memberContext[$message['id_member_from']]['show_profile_buttons'] = $settings['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'])) || (in_array($memberContext[$message['id_member_from']]['show_email'], array('yes', 'yes_permission_override', 'no_through_forum'))) || $context['can_send_pm']);
3071
3072
	// Censor all the important text...
3073
	$message['body'] = censor($message['body']);
3074
	$message['subject'] = censor($message['subject']);
3075
3076
	// Run BBC interpreter on the message.
3077
	$bbc_parser = \BBC\ParserWrapper::instance();
3078
	$message['body'] = $bbc_parser->parsePM($message['body']);
3079
3080
	// Return the array.
3081
	$output = array(
3082
		'alternate' => $counter % 2,
3083
		'id' => $message['id_pm'],
3084
		'member' => &$memberContext[$message['id_member_from']],
3085
		'subject' => $message['subject'],
3086
		'time' => standardTime($message['msgtime']),
3087
		'html_time' => htmlTime($message['msgtime']),
3088
		'timestamp' => forum_time(true, $message['msgtime']),
3089
		'counter' => $counter,
3090
		'body' => $message['body'],
3091
		'recipients' => &$recipients[$message['id_pm']],
3092
		'number_recipients' => count($recipients[$message['id_pm']]['to']),
3093
		'labels' => &$context['message_labels'][$message['id_pm']],
3094
		'fully_labeled' => count($context['message_labels'][$message['id_pm']]) == count($context['labels']),
3095
		'is_replied_to' => &$context['message_replied'][$message['id_pm']],
3096
		'is_unread' => &$context['message_unread'][$message['id_pm']],
3097
		'is_selected' => !empty($temp_pm_selected) && in_array($message['id_pm'], $temp_pm_selected),
3098
		'is_message_author' => $message['id_member_from'] == $user_info['id'],
3099
		'can_report' => !empty($modSettings['enableReportPM']),
3100
		'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member_from'] == $user_info['id'] && !empty($user_info['id'])),
3101
	);
3102
3103
	$context['additional_pm_drop_buttons'] = array();
3104
3105
	// Can they report this message
3106
	if (!empty($output['can_report']) && $context['folder'] !== 'sent' && $output['member']['id'] != $user_info['id'])
3107
	{
3108
		$context['additional_pm_drop_buttons']['warn_button'] = array(
3109
			'href' => $scripturl . '?action=pm;sa=report;l=' . $context['current_label_id'] . ';pmsg=' . $output['id'] . ';' . $context['session_var'] . '=' . $context['session_id'],
3110
			'text' => $txt['pm_report_to_admin']
3111
		);
3112
	}
3113
3114
	// Or mark it as unread
3115
	if (empty($output['is_unread']) && $context['folder'] !== 'sent' && $output['member']['id'] != $user_info['id'])
3116
	{
3117
		$context['additional_pm_drop_buttons']['restore_button'] = array(
3118
			'href' => $scripturl . '?action=pm;sa=markunread;l=' . $context['current_label_id'] . ';pmsg=' . $output['id'] . ';' . $context['session_var'] . '=' . $context['session_id'],
3119
			'text' => $txt['pm_mark_unread']
3120
		);
3121
	}
3122
3123
	// Or give / take karma for a PM
3124
	if (!empty($output['member']['karma']['allow']))
3125
	{
3126
		$output['member']['karma'] += array(
3127
			'applaud_url' => $scripturl . '?action=karma;sa=applaud;uid=' . $output['member']['id'] . ';f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pm=' . $output['id'] . ';' . $context['session_var'] . '=' . $context['session_id'],
3128
			'smite_url' => $scripturl . '?action=karma;sa=smite;uid=' . $output['member']['id'] . ';f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] != -1 ? ';l=' . $context['current_label_id'] : '') . ';pm=' . $output['id'] . ';' . $context['session_var'] . '=' . $context['session_id'],
3129
		);
3130
	}
3131
3132
	$counter++;
3133
3134
	return $output;
3135
}
3136