Post::action_quotefast()   B
last analyzed

Complexity

Conditions 8
Paths 20

Size

Total Lines 66
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 33
nc 20
nop 0
dl 0
loc 66
ccs 0
cts 28
cp 0
crap 72
rs 8.1475
c 0
b 0
f 0

How to fix   Long Method   

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
 * The job of this file is to handle everything related to posting replies,
5
 * new topics, quotes, and modifications to existing posts.  It also handles
6
 * quoting posts by way of JavaScript.
7
 *
8
 * @package   ElkArte Forum
9
 * @copyright ElkArte Forum contributors
10
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
11
 *
12
 * This file contains code covered by:
13
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
14
 *
15
 * @version 2.0 Beta 1
16
 *
17
 */
18
19
namespace ElkArte\Controller;
20
21
use BBC\ParserWrapper;
22
use BBC\PreparseCode;
23
use ElkArte\AbstractController;
24
use ElkArte\Cache\Cache;
25
use ElkArte\Errors\ErrorContext;
26
use ElkArte\Exceptions\ControllerRedirectException;
27
use ElkArte\Exceptions\Exception;
28
use ElkArte\Helper\DataValidator;
29
use ElkArte\Helper\Util;
30
use ElkArte\Languages\Txt;
31
use ElkArte\Notifications\Notifications;
32
use ElkArte\Notifications\NotificationsTask;
33
use ElkArte\Themes\TemplateLayers;
34
use ElkArte\User;
35
36
/**
37
 * Everything related to posting new replies and topics and modifications of them
38
 */
39
class Post extends AbstractController
40
{
41
	/** @var null|ErrorContext The post (messages) errors object */
42
	protected $_post_errors;
43
44
	/** @var null|TemplateLayers The template layers object */
45
	protected $_template_layers;
46
47
	/** @var array An array of attributes of the topic (if not new) */
48
	protected $_topic_attributes = [];
49
50
	/** @var string The message subject */
51
	protected $_form_subject = '';
52
53
	/** @var string The message */
54
	protected $_form_message = '';
55
56
	/** @var PreparseCode */
57
	protected $preparse;
58
59
	/**
60 6
	 * Sets up common stuff for all or most of the actions.
61
	 */
62 6
	public function pre_dispatch()
63 6
	{
64
		$this->_post_errors = ErrorContext::context('post', 1);
65 6
		$this->_template_layers = theme()->getLayers();
66
67 6
		$this->preparse = PreparseCode::instance($this->user->name);
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
68 6
69 6
		require_once(SUBSDIR . '/Post.subs.php');
70 6
		require_once(SUBSDIR . '/Messages.subs.php');
71
		require_once(SUBSDIR . '/Topic.subs.php');
72
	}
73
74
	/**
75
	 * Dispatch to the right action method for the request.
76
	 *
77
	 * @see AbstractController::action_index
78
	 */
79
	public function action_index()
80
	{
81
		// Figure out the right action to do.
82
		// hint: I'm post controller. :P
83
		$this->action_post();
84
	}
85
86
	/**
87
	 * Handles showing the post-screen, loading the post to be modified, loading any post quoted, previews,
88
	 * display of errors and polls.
89
	 *
90
	 * What it does:
91
	 *
92
	 * - Validates that we're posting in a board.
93
	 * - Find the topic id if a message id is passed, else assume it's a new message
94
	 * - Get the response prefix in the default forum language.
95
	 * - Triggers events associated with posting.
96
	 *    - prepare_post, prepare_context, prepare_modifying, prepare_editing,
97
	 *    - prepare_posting, post_errors, finalize_post_form
98
	 * - Additionally handles previews of posts.
99
	 * - Requires different permissions depending on the actions, but most notably post_new, post_reply_own, and post_reply_any.
100
	 * - Shows options for the editing and posting of calendar events and attachments, and as the posting of polls (using modules).
101
	 * - Accessed from ?action=post or called from action_post2 in the event of errors or preview from quick reply.
102
	 *
103
	 * @uses the Post template and language file, main sub template.
104
	 * @uses Errors language
105
	 */
106
	public function action_post()
107
	{
108
		global $context;
109
110
		// Initialize the post-area
111
		$this->_beforePreparePost();
112
113
		// Trigger the prepare_post event
114
		$this->_events->trigger('prepare_post', ['topic_attributes' => &$this->_topic_attributes]);
115
116
		// Sets post form options / checkbox / etc
117
		$this->_beforePrepareContext();
118
119
		// Trigger the prepare_context event
120
		try
121
		{
122
			$this->_events->trigger('prepare_context', ['id_member_poster' => (int) $this->_topic_attributes['id_member']]);
123
		}
124
		catch (ControllerRedirectException $controllerRedirectException)
125
		{
126
			return $controllerRedirectException->doRedirect($this);
127
		}
128
129
		// Load up the message details if this is an existing msg
130
		$this->_generatingMessage();
131
132
		// Trigger post_errors event
133
		$this->_events->trigger('post_errors');
134
135
		$this->_preparingPage();
136
137
		// Needed for the editor and message icons.
138
		require_once(SUBSDIR . '/Editor.subs.php');
139
140
		// Now create the editor.
141
		$editorOptions = [
142
			'id' => 'message',
143
			'value' => $context['message'],
144
			'labels' => [
145
				'post_button' => $context['submit_label'],
146
			],
147
			// add height and width for the editor
148
			'height' => '275px',
149
			'width' => '100%',
150
			// We do an XML preview here.
151
			'preview_type' => 2,
152
			'smiley_container' => 'smileyBox_message',
153
			'bbc_container' => 'bbcBox_message',
154
			'live_errors' => 1
155
		];
156
157
		// Trigger the finalize_post_form event
158
		$this->_events->trigger('finalize_post_form', ['destination' => &$context['destination'], 'page_title' => &$context['page_title'], 'show_additional_options' => &$context['show_additional_options'], 'editorOptions' => &$editorOptions]);
159
160
		// Initialize the editor
161
		create_control_richedit($editorOptions);
162
163
		$this->_finalizePage();
164
		return null;
165
	}
166
167
	/**
168
	 * Load language files, templates and prepare posting basics
169
	 */
170
	protected function _beforePreparePost(): void
171
	{
172
		global $context;
173
174
		Txt::load('Post');
175
		Txt::load('Errors');
176
177
		$context['robot_no_index'] = true;
178
		$this->_template_layers->add('postarea');
0 ignored issues
show
Bug introduced by
The method add() does not exist on null. ( Ignorable by Annotation )

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

178
		$this->_template_layers->/** @scrutinizer ignore-call */ 
179
                           add('postarea');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
179
		$this->_topic_attributes = [
180
			'locked' => 0,
181
			'notify' => 0,
182
			'is_sticky' => 0,
183
			'id_last_msg' => 0,
184
			'id_member' => 0,
185
			'id_first_msg' => 0,
186
			'subject' => '',
187
			'last_post_time' => 0
188
		];
189
	}
190
191
	/**
192
	 * Does some basic checking and if everything is valid will
193
	 * load $context with necessary post form options
194
	 *
195
	 * - Ensures we have a topic id
196
	 * - Checks if a topic is locked
197
	 * - Determines if this msg will be pre-approved or member requires approval
198
	 *
199
	 * @throws Exception
200
	 */
201
	protected function _beforePrepareContext(): void
202
	{
203
		global $topic, $modSettings, $board, $context;
204
205
		// You must be posting to *some* board.
206
		if (empty($board) && !$context['make_event'])
207
		{
208
			throw new Exception('no_board', false);
209
		}
210
211
		// All those wonderful modifiers and attachments
212
		$this->_template_layers->add('additional_options', 200);
213
214
		if ($this->getApi() !== false)
215
		{
216
			$context['sub_template'] = 'post';
217
218
			// Just in case of an earlier error...
219
			$context['preview_message'] = '';
220
			$context['preview_subject'] = '';
221
		}
222
223
		// No message is complete without a topic.
224
		if (empty($topic) && !empty($_REQUEST['msg']))
225
		{
226
			$topic = associatedTopic((int) $_REQUEST['msg']);
227
			if (empty($topic))
228
			{
229
				$this->_req->clearValue('msg', 'both');
230
			}
231
		}
232
233
		// Check if it's locked. It isn't locked if no topic is specified.
234
		if (!empty($topic))
235
		{
236
			$this->_topic_attributes = topicUserAttributes($topic, $this->user->id);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
237
			$context['notify'] = $this->_topic_attributes['notify'];
238
			$context['topic_last_message'] = $this->_topic_attributes['id_last_msg'];
239
			$msg = $this->_req->getRequest('msg', 'intval', 0);
240
241
			if (empty($msg))
242
			{
243
				if ($this->user->is_guest && !allowedTo('post_reply_any')
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
244
					&& (!$modSettings['postmod_active'] || !allowedTo('post_unapproved_replies_any')))
245
				{
246
					is_not_guest();
247
				}
248
249
				// By default, the reply will be approved...
250
				$context['becomes_approved'] = true;
251
				if ($this->_topic_attributes['id_member'] != $this->user->id)
252
				{
253
					if ($modSettings['postmod_active'] && allowedTo('post_unapproved_replies_any')
254
						&& !allowedTo('post_reply_any'))
255
					{
256
						$context['becomes_approved'] = false;
257
					}
258
					else
259
					{
260
						isAllowedTo('post_reply_any');
261
					}
262
				}
263
				elseif (!allowedTo('post_reply_any'))
264
				{
265
					if ($modSettings['postmod_active'])
266
					{
267
						if (allowedTo('post_unapproved_replies_own') && !allowedTo('post_reply_own'))
268
						{
269
							$context['becomes_approved'] = false;
270
						}
271
						// Guests do not have post_unapproved_replies_own permission, so it's always post_unapproved_replies_any
272
						elseif ($this->user->is_guest && allowedTo('post_unapproved_replies_any'))
273
						{
274
							$context['becomes_approved'] = false;
275
						}
276
						else
277
						{
278
							isAllowedTo('post_reply_own');
279
						}
280
					}
281
					else
282
					{
283
						isAllowedTo('post_reply_own');
284
					}
285
				}
286
			}
287
			else
288
			{
289
				$context['becomes_approved'] = true;
290
			}
291
292
			$context['can_lock'] = allowedTo('lock_any') || ($this->user->id == $this->_topic_attributes['id_member'] && allowedTo('lock_own'));
293
			$context['can_sticky'] = allowedTo('make_sticky');
294
			$context['notify'] = !empty($context['notify']);
295
			$context['sticky'] = isset($_REQUEST['sticky']) ? !empty($_REQUEST['sticky']) : $this->_topic_attributes['is_sticky'];
296
		}
297
		else
298
		{
299
			$this->_topic_attributes['id_member'] = 0;
300
			$context['becomes_approved'] = true;
301
			if (empty($context['make_event']) || !empty($board))
302
			{
303
				if ($modSettings['postmod_active'] && !allowedTo('post_new') && allowedTo('post_unapproved_topics'))
304
				{
305
					$context['becomes_approved'] = false;
306
				}
307
				else
308
				{
309
					isAllowedTo('post_new');
310
				}
311
			}
312
313
			$this->_topic_attributes['locked'] = 0;
314
315
			// @todo These won't work if you're making an event.
316
			$context['can_lock'] = allowedTo(['lock_any', 'lock_own']);
317
			$context['can_sticky'] = allowedTo('make_sticky');
318
319
			$context['notify'] = !empty($context['notify']);
320
			$context['sticky'] = !empty($_REQUEST['sticky']);
321
		}
322
323
		// @todo These won't work if you're posting an event!
324
		$context['can_notify'] = allowedTo('mark_any_notify');
325
		$context['can_move'] = allowedTo('move_any');
326
		$context['move'] = !empty($_REQUEST['move']);
327
		$context['announce'] = !empty($_REQUEST['announce']);
328
		$context['id_draft'] = $this->_req->getPost('id_draft', 'intval', 0);
329
330
		// You can only announce topics that will get approved...
331
		$context['can_announce'] = allowedTo('announce_topic') && $context['becomes_approved'];
332
		$context['locked'] = !empty($this->_topic_attributes['locked']) || !empty($_REQUEST['lock']);
333
		$context['can_quote'] = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
334
335
		// Generally don't show the approval box... (Assume we want things approved)
336
		$context['show_approval'] = allowedTo('approve_posts') && $context['becomes_approved'] ? 2 : (allowedTo('approve_posts') ? 1 : 0);
337
338
		// Don't allow a post if it's locked, and you aren't all powerful.
339
		if (!$this->_topic_attributes['locked'])
340
		{
341
			return;
342
		}
343
344
		if (allowedTo('moderate_board'))
345
		{
346
			return;
347
		}
348
349
		throw new Exception('topic_locked', false);
350
	}
351
352
	/**
353
	 * Get the message set up for ...
354
	 *
355
	 * - Sets up the form for preview / modify / new message status.  Items
356
	 * such as icons, text, etc.
357
	 * - Look if a new topic was posted while working on this prose
358
	 * - Shows the message preview if requested
359
	 * - Triggers prepare_modifying, prepare_editing, prepare_posting
360
	 */
361
	protected function _generatingMessage(): void
362
	{
363
		global $txt, $topic, $modSettings, $context, $options, $board_info;
364
365
		// Convert / Clean the input elements
366
		$msg = $this->_req->getRequest('msg', 'intval');
367
		$last_msg = $this->_req->getRequest('last_msg', 'intval');
368
		$message = $this->_req->getPost('message', 'trim');
369
		$subject = $this->_req->getPost('subject', 'trim', '');
370
371
		// See if any new replies have come along.
372
		if (empty($msg)
373
			&& !empty($topic)
374
			&& empty($options['no_new_reply_warning'])
375
			&& isset($last_msg)
376
			&& $context['topic_last_message'] > $last_msg)
377
		{
378
			$context['new_replies'] = countMessagesSince($topic, (int) $_REQUEST['last_msg'], false, $modSettings['postmod_active'] && !allowedTo('approve_posts'));
379
380
			if (!empty($context['new_replies']))
381
			{
382
				if ($context['new_replies'] === 1)
383
				{
384
					$txt['error_new_replies'] = isset($_GET['last_msg']) ? $txt['error_new_reply_reading'] : $txt['error_new_reply'];
385
				}
386
				else
387
				{
388
					$txt['error_new_replies'] = sprintf(isset($_GET['last_msg']) ? $txt['error_new_replies_reading'] : $txt['error_new_replies'], $context['new_replies']);
389
				}
390
391
				$this->_post_errors->addError('new_replies', 0);
0 ignored issues
show
Bug introduced by
The method addError() does not exist on null. ( Ignorable by Annotation )

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

391
				$this->_post_errors->/** @scrutinizer ignore-call */ 
392
                         addError('new_replies', 0);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
392
393
				$modSettings['topicSummaryPosts'] = $context['new_replies'] > $modSettings['topicSummaryPosts'] ? max($modSettings['topicSummaryPosts'], 5) : $modSettings['topicSummaryPosts'];
394
			}
395
		}
396
397
		// Get a response prefix (like 'Re:') in the default forum language.
398
		$context['response_prefix'] = response_prefix();
399
		$context['destination'] = 'post2;start=' . $this->_req->getRequest('start', 'intval', 0);
400
401
		// Previewing, modifying, or posting?
402
		// Do we have a body, but an error happened?
403
		if (isset($message) || $this->_post_errors->hasErrors())
404
		{
405
			$this->_previewPost($msg, $topic, $message, $subject);
406
		}
407
		// Editing a message...
408
		elseif (isset($msg) && !empty($topic))
409
		{
410
			$this->_editPost($msg, $topic);
411
		}
412
		// Posting...
413
		else
414
		{
415
			$this->_makePost($topic, $subject);
416
		}
417
418
		// Check whether this is an ancient post being bumped...
419
		if (!empty($topic)
420
			&& !empty($board_info['old_posts'])
421
			&& !empty($modSettings['oldTopicDays'])
422
			&& $this->_topic_attributes['last_post_time'] + $modSettings['oldTopicDays'] * 86400 < time()
423
			&& empty($this->_topic_attributes['is_sticky'])
424
			&& !isset($_REQUEST['subject']))
425
		{
426
			$this->_post_errors->addError(['old_topic', [$modSettings['oldTopicDays']]], 0);
427
		}
428
	}
429
430
	/**
431
	 * Preview a post,
432
	 * - From pressing preview
433
	 * - When errors are generated when trying to post.
434
	 *
435
	 * @param int $msg
436
	 * @param int $topic
437
	 * @param string $message
438
	 * @param string $subject
439
	 */
440
	private function _previewPost($msg, $topic, $message, $subject): void
441
	{
442
		global $txt, $modSettings, $context;
443
444
		$preview = $this->_req->getPost('preview', 'isset', false);
445
		$more_options = $this->_req->getPost('more_options', 'isset', false);
446
		$ns = $this->_req->getPost('ns', 'isset', false);
447
		$notify = $this->_req->getPost('notify', 'intval', 0);
448
		$quote = $this->_req->getRequest('quote', 'intval', 0);
449
		$followup = $this->_req->getRequest('followup', 'intval', 0);
450
		$not_approved = $this->_req->getPost('not_approved', 'empty', true);
451
		$last_msg = $this->_req->getRequest('last_msg', 'intval');
452
		$icon = $this->_req->getPost('icon', 'trim', 'xx');
453
		$msg_id = 0;
454
455
		// Validate inputs if they are not just moving the full post form (from QR / QT)
456
		if (!$more_options && !$this->_post_errors->hasErrors())
457
		{
458
			// This means they didn't click Post and get an error.
459
			$really_previewing = true;
460
		}
461
		else
462
		{
463
			if (!isset($message))
464
			{
465
				$message = '';
466
			}
467
468
			// They are previewing if they asked to preview (i.e., came from quick reply).
469
			$really_previewing = !empty($preview) || ($this->getApi() === 'xml');
470
		}
471
472
		// Trigger the prepare_modifying event
473
		$this->_events->trigger('prepare_modifying', [
474
			'post_errors' => $this->_post_errors,
475
			'really_previewing' => &$really_previewing]
476
		);
477
478
		// To keep the approval status flowing through, we have to pass it through the form...
479
		$context['becomes_approved'] = $not_approved;
480
		$approve = $this->_req->getPost('approve', 'intval');
481
		$context['show_approval'] = $approve !== null ? ($approve ? 2 : 1) : 0;
482
		$context['can_announce'] = $context['can_announce'] && $context['becomes_approved'];
483
484
		// Set up the inputs for the form.
485
		$this->_form_subject = strtr(Util::htmlspecialchars($subject), ["\r" => '', "\n" => '', "\t" => '']);
486
		$this->_form_message = Util::htmlspecialchars($message, ENT_QUOTES, 'UTF-8', true);
487
488
		// Make sure the subject isn't too long - taking into account special characters.
489
		if (Util::strlen($this->_form_subject) > 100)
490
		{
491
			$this->_form_subject = Util::substr($this->_form_subject, 0, 100);
492
		}
493
494
		// Are you... a guest?
495
		if ($this->user->is_guest)
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
496
		{
497
			$context['name'] = $this->_req->getPost('guestname', 'Util::htmlspecialchars', '');
498
			$context['email'] = $this->_req->getPost('email', 'Util::htmlspecialchars', '');
499
			$this->user->name = $context['name'];
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __set, consider adding a @property annotation.
Loading history...
500
		}
501
502
		// Only show the preview stuff if they really hit Preview, not post, not more options
503
		if ($really_previewing)
504
		{
505
			$this->_setupPreviewContext(!$ns);
506
		}
507
508
		// Set up the checkboxes.
509
		$context['notify'] = $notify;
510
		$context['use_smileys'] = !$ns;
511
		$context['icon'] = preg_replace('~[./*\':"<>]~', '', $icon);
512
513
		// Set the destination action for submission.
514
		$context['destination'] .= isset($msg) ? ';msg=' . $msg . ';' . $context['session_var'] . '=' . $context['session_id'] : '';
515
		$context['submit_label'] = isset($msg) ? $txt['save'] : $txt['post'];
516
517
		// Previewing an edit? Modifying an existing message?
518
		if (isset($msg) && !empty($topic))
519
		{
520
			// Get the existing message.
521
			$message = messageDetails($msg, $topic);
522
523
			// The message they were trying to edit was most likely deleted.
524
			if ($message === false)
0 ignored issues
show
introduced by
The condition $message === false is always false.
Loading history...
525
			{
526
				throw new Exception('no_message', false);
527
			}
528
529
			$errors = checkMessagePermissions($message['message']);
530
			if (!empty($errors))
531
			{
532
				foreach ($errors as $error)
533
				{
534
					$this->_post_errors->addError($error);
535
				}
536
			}
537
538
			prepareMessageContext($message);
539
		}
540
		elseif (isset($last_msg))
541
		{
542
			// @todo: sort out what kind of combinations are actually possible
543
			// Posting a quoted reply?
544
			if ((!empty($topic) && !empty($quote))
545
				|| (!empty($modSettings['enableFollowup']) && !empty($followup)))
546
			{
547
				$msg_id = empty($quote) ? $followup : $quote;
548
				$case = 2;
549
			}
550
			// Posting a reply without a quote?
551
			elseif (!empty($topic) && empty($quote))
552
			{
553
				$subject = strtr(Util::htmlspecialchars($subject), ["\r" => '', "\n" => '', "\t" => '']);
554
				$this->_topic_attributes['subject'] = $subject;
555
				$case = 3;
556
			}
557
			else
558
			{
559
				$case = 4;
560
			}
561
562
			[$this->_form_subject,] = getFormMsgSubject($case, $topic, $this->_topic_attributes['subject'], $msg_id);
563
		}
564
565
		// No check is needed, since nothing is really posted.
566
		checkSubmitOnce('free');
567
	}
568
569
	/**
570
	 * Going back to a message to make changes, like damn, I should watch what
571
	 * my fingers are typing.
572
	 *
573
	 * @param int $msg
574
	 * @param int $topic
575
	 */
576
	private function _editPost($msg, $topic): void
577
	{
578
		global $txt, $context;
579
580
		$message = getFormMsgSubject(1, $topic, '', $msg);
581
582
		// The message they were trying to edit was most likely deleted.
583
		if ($message === false)
0 ignored issues
show
introduced by
The condition $message === false is always false.
Loading history...
584
		{
585
			throw new Exception('no_message', false);
586
		}
587
588
		// Trigger the prepare_editing event
589
		$this->_events->trigger('prepare_editing', ['topic' => $topic, 'message' => &$message]);
590
591
		if (!empty($message['errors']))
592
		{
593
			foreach ($message['errors'] as $error)
594
			{
595
				$this->_post_errors->addError($error);
596
			}
597
		}
598
599
		// Get the stuff ready for the form.
600
		$this->_form_subject = censor($message['message']['subject']);
601
		$this->_form_message = censor($this->preparse->un_preparsecode($message['message']['body']));
602
603
		// Check the boxes that should be checked.
604
		$context['use_smileys'] = !empty($message['message']['smileys_enabled']);
605
		$context['icon'] = $message['message']['icon'];
606
607
		// Set the destination.
608
		$context['destination'] .= ';msg=' . $msg . ';' . $context['session_var'] . '=' . $context['session_id'];
609
		$context['submit_label'] = $txt['save'];
610
	}
611
612
	/**
613
	 * Think you are done and ready to make a post
614
	 *
615
	 * @param int $topic
616
	 * @param string $subject
617
	 */
618
	private function _makePost($topic, $subject): void
619
	{
620
		global $context, $txt, $modSettings;
621
622
		$quote = $this->_req->getRequest('quote', 'intval', 0);
623
		$followup = $this->_req->getRequest('followup', 'intval', 0);
624
625
		// By default....
626
		$context['use_smileys'] = true;
627
		$context['icon'] = 'xx';
628
		$msg_id = 0;
629
630
		if ($this->user->is_guest)
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
631
		{
632
			$context['name'] = $_SESSION['guest_name'] ?? '';
633
			$context['email'] = $_SESSION['guest_email'] ?? '';
634
		}
635
636
		// Trigger the prepare_posting event
637
		$this->_events->trigger('prepare_posting');
638
639
		$context['submit_label'] = $txt['post'];
640
641
		// @todo: sort out what kind of combinations are actually possible
642
		// Posting a quoted reply?
643
		if ((!empty($topic) && $quote !== 0)
644
			|| (!empty($modSettings['enableFollowup']) && $followup !== 0))
645
		{
646
			$case = 2;
647
			$msg_id = empty($quote) ? $followup : $quote;
648
		}
649
		// Posting a reply without a quote?
650
		elseif (!empty($topic) && $quote === 0)
651
		{
652
			$case = 3;
653
		}
654
		else
655
		{
656
			$this->_topic_attributes['subject'] = $subject;
657
			$case = 4;
658
		}
659
660
		[$this->_form_subject, $this->_form_message] = getFormMsgSubject($case, $topic, $this->_topic_attributes['subject'], $msg_id);
661
	}
662
663
	/**
664
	 * Loads up the context global with a preview of the post
665
	 *
666
	 * @param bool $ns no smiley flag
667
	 */
668
	private function _setupPreviewContext($ns): void
669
	{
670
		global $txt, $modSettings, $context;
671
672
		// Set up the preview message and subject
673
		$context['preview_message'] = $this->_form_message;
674
		$this->preparse->preparsecode($this->_form_message, true);
675
676
		// Do all bulletin board code thing on the message
677
		$bbc_parser = ParserWrapper::instance();
678
		$this->preparse->preparsecode($context['preview_message']);
679
		$context['preview_message'] = $bbc_parser->parseMessage($context['preview_message'], $ns);
680
		$context['preview_message'] = censor($context['preview_message']);
681
682
		// Remember the subject
683
		$context['preview_subject'] = censor($this->_form_subject);
684
685
		// Any errors we should tell them about?
686
		if ($this->_form_subject === '')
687
		{
688
			$this->_post_errors->addError('no_subject');
689
			$context['preview_subject'] = '<em>' . $txt['no_subject'] . '</em>';
690
		}
691
692
		if ($context['preview_message'] === '')
693
		{
694
			$this->_post_errors->addError('no_message');
695
		}
696
		elseif (!empty($modSettings['max_messageLength']) && Util::strlen($this->_form_message) > $modSettings['max_messageLength'])
697
		{
698
			$this->_post_errors->addError(['long_message', [$modSettings['max_messageLength']]]);
699
		}
700
701
		// Protect any CDATA blocks.
702
		if ($this->getApi() === 'xml')
703
		{
704
			$context['preview_message'] = strtr($context['preview_message'], [']]>' => ']]]]><![CDATA[>']);
705
		}
706
	}
707
708
	/**
709
	 * Preparing the page for post-preview or error handling.
710
	 */
711
	protected function _preparingPage(): void
712
	{
713
		global $txt, $topic, $modSettings, $board, $context;
714
715
		// Any errors occurred?
716
		$context['post_error'] = [
717
			'errors' => $this->_post_errors->prepareErrors(),
718
			'type' => $this->_post_errors->getErrorType() === 0 ? 'minor' : 'serious',
719
			'title' => $this->_post_errors->getErrorType() === 0 ? $txt['warning_while_submitting'] : $txt['error_while_submitting'],
720
		];
721
722
		// What are you doing? Posting, modifying, previewing, new post, or reply...
723
		if (empty($context['page_title']))
724
		{
725
			if (isset($_REQUEST['msg']))
726
			{
727
				$context['page_title'] = $txt['modify_msg'];
728
			}
729
			elseif (isset($_REQUEST['subject'], $context['preview_subject']))
730
			{
731
				$context['page_title'] = $txt['post_reply'];
732
			}
733
			elseif (empty($topic))
734
			{
735
				$context['page_title'] = $txt['start_new_topic'];
736
			}
737
			else
738
			{
739
				$context['page_title'] = $txt['post_reply'];
740
			}
741
		}
742
743
		// Update the topic summary, needed to show new posts in a preview
744
		if (!empty($topic) && !empty($modSettings['topicSummaryPosts']))
745
		{
746
			$only_approved = $modSettings['postmod_active'] && !allowedTo('approve_posts');
747
748
			if ($this->getApi() === 'xml')
749
			{
750 6
				$limit = empty($context['new_replies']) ? 0 : (int) $context['new_replies'];
751
			}
752 6
			else
753 6
			{
754
				$limit = $modSettings['topicSummaryPosts'];
755
			}
756 6
757
			$before = isset($_REQUEST['msg']) ? ['before' => (int) $_REQUEST['msg']] : [];
758
759
			$counter = 0;
760
			$context['previous_posts'] = empty($limit) ? [] : selectMessages($topic, 0, $limit, $before, $only_approved);
761
			foreach ($context['previous_posts'] as &$post)
762
			{
763
				$post['is_new'] = !empty($context['new_replies']);
764
				$post['counter'] = $counter++;
765
				$post['is_ignored'] = !empty($modSettings['enable_buddylist']) && in_array($post['id_poster'], $this->user->ignoreusers);
0 ignored issues
show
Bug introduced by
It seems like $this->user->ignoreusers can also be of type null; however, parameter $haystack of in_array() does only seem to accept array, 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

765
				$post['is_ignored'] = !empty($modSettings['enable_buddylist']) && in_array($post['id_poster'], /** @scrutinizer ignore-type */ $this->user->ignoreusers);
Loading history...
Bug Best Practice introduced by
The property ignoreusers does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
766
767 6
				if (!empty($context['new_replies']))
768
				{
769
					$context['new_replies']--;
770
				}
771
			}
772
773 6
			// If there are previous posts, enable QQ for them
774
			if (count($context['previous_posts']) > 0)
775
			{
776 6
				loadJavascriptFile('quickQuote.js', ['defer' => true]);
777
				theme()->addInlineJavascript("
778
					document.addEventListener('DOMContentLoaded', () => new Elk_QuickQuote(), false);", true
779 6
				);
780
			}
781
		}
782
783
		// Just ajax previewing, then let's stop now
784
		if ($this->getApi() === 'xml')
785
		{
786
			obExit();
787
		}
788
789 6
		$context['subject'] = addcslashes($this->_form_subject, '"');
790
		$context['message'] = str_replace(['"', '<', '>', '&nbsp;'], ['&quot;', '&lt;', '&gt;', ' '], $this->_form_message);
791
792 6
		// Message icons - customized or not, retrieve them...
793
		require_once(SUBSDIR . '/MessageIcons.subs.php');
794
		$context['icons'] = array_values(getMessageIcons($board));
795
796
		$context['icon_url'] = '';
797 6
798 6
		if (!empty($context['icons']))
799
		{
800
			$context['icons'][count($context['icons']) - 1]['is_last'] = true;
801 6
			$context['icons'][0]['selected'] = true;
802
803
			// $context['icon'] is set when editing a message
804 6
			if (!isset($context['icon']))
805
			{
806
				$context['icon'] = $context['icons'][0]['value'];
807 6
			}
808
809 4
			$found = false;
810
			foreach ($context['icons'] as $icon)
811
			{
812 4
				if ($icon['value'] === $context['icon'])
813
				{
814
					$found = true;
815
					$context['icon_url'] = $icon['url'];
816
					break;
817
				}
818 4
			}
819
820
			// Fail-safe
821
			if (!$found)
822
			{
823
				$context['icon'] = $context['icons'][0]['value'];
824
				$context['icon_url'] = $context['icons'][0]['url'];
825 6
			}
826
		}
827
828 2
		$context['show_additional_options'] = !empty($_POST['additional_options']) || isset($_GET['additionalOptions']);
829
	}
830
831
	/**
832
	 * Finalizes the page setup for the post form, including the link tree, context flags, and template loading.
833
	 * - Determines whether the action is for creating a new topic, a new post or editing an existing post.
834 2
	 * - Updates breadcrumb navigation based on the topic or new topic creation.
835 2
	 * - Registers the form to prevent duplicate submissions.
836
	 * - Loads the appropriate template for rendering the post form if not using an API.
837 2
	 *
838
	 * @return void
839
	 */
840
	protected function _finalizePage(): void
841
	{
842
		global $txt, $scripturl, $topic, $context;
843 2
844
		// Build the link tree.
845
		if (empty($topic))
846
		{
847
			$context['breadcrumbs'][] = [
848
				'name' => '<em>' . $txt['start_new_topic'] . '</em>'
849
			];
850
		}
851
		else
852
		{
853
			$context['breadcrumbs'][] = [
854
				'url' => $scripturl . '?topic=' . $topic . '.' . $_REQUEST['start'],
855
				'name' => strtr(Util::htmlspecialchars($this->_form_subject), ["\r" => '', "\n" => '', "\t" => '']),
856
				'extra_before' => '<span><strong class="nav">' . $context['page_title'] . ' ( </strong></span>',
857
				'extra_after' => '<span><strong class="nav"> )</strong></span>'
858
			];
859
		}
860
861
		$context['back_to_topic'] = isset($_REQUEST['goback']) || (isset($_REQUEST['msg']) && !isset($_REQUEST['subject']));
862
		$context['is_new_topic'] = empty($topic);
863
		$context['is_new_post'] = !isset($_REQUEST['msg']);
864
		$context['is_first_post'] = $context['is_new_topic'] || (isset($_REQUEST['msg']) && $_REQUEST['msg'] == $this->_topic_attributes['id_first_msg']);
865
		$context['current_action'] = 'post';
866 2
867
		// Register this form in the session variables.
868
		checkSubmitOnce('register');
869
870
		// Finally, load the template.
871
		if ($this->getApi() === false)
872 2
		{
873
			theme()->getTemplates()->load('Post');
874
			$context['sub_template'] = 'post_page';
875
		}
876
	}
877
878 2
	/**
879
	 * Posts or saves the message composed with Post().
880
	 *
881 2
	 * What it does:
882
	 *
883
	 * - Requires various permissions depending on the action.
884
	 * - Handles attachment, post, and calendar saving.
885
	 * - Sends off notifications, and allows for announcements and moderation.
886
	 * - Accessed from ?action=post2.
887
	 * - Triggers events associated with the actual posting
888
	 *   - prepare_save_post, save_replying, save_new_topic, save_modify
889
	 *   - before_save_post, pre_save_post, after_save_post
890
	 */
891
	public function action_post2()
892 2
	{
893
		global $board, $topic, $txt, $modSettings, $context, $board_info, $options;
894
895
		// Sneaking off, are we?
896
		if (empty($_POST) && empty($topic))
897
		{
898 2
			if (empty($_SERVER['CONTENT_LENGTH']))
899
			{
900
				redirectexit('action=post;board=' . $board . '.0');
901 2
			}
902 2
			else
903
			{
904
				throw new Exception('post_upload_error', false);
905
			}
906
		}
907
		elseif (empty($_POST) && !empty($topic))
908 2
		{
909
			redirectexit('action=post;topic=' . $topic . '.0');
910
		}
911
912 2
		// No need!
913
		$context['robot_no_index'] = true;
914 2
915
		// We are now in post2 action
916
		$context['current_action'] = 'post2';
917
918
		// If the session has timed out, let the user re-submit their form.
919 2
		if (checkSession('post', '', false) !== '')
920
		{
921
			$this->_post_errors->addError('session_timeout');
922
923
			// Disable the preview so that any potentially malicious code is not executed
924 2
			$_REQUEST['preview'] = false;
925
926
			return $this->action_post();
927 2
		}
928
929 2
		// Shortcut to save some typing
930
		$req = $this->_req;
931 2
932
		$topic_info = [];
933 2
934
		// Previewing? Go back to start.
935
		if (isset($_REQUEST['preview']) || $req->hasPost('more_options'))
936
		{
937
			return $this->action_post();
938
		}
939 2
940
		require_once(SUBSDIR . '/Boards.subs.php');
941 2
		Txt::load('Post');
942
943
		// Trigger the prepare_save_post event
944
		$this->_events->trigger('prepare_save_post', ['topic_info' => &$topic_info]);
945
946 2
		// Prevent double submission of this form.
947
		checkSubmitOnce('check');
948 2
949
		// If this isn't a new topic, load the topic info that we need.
950
		if (!empty($topic))
951
		{
952 2
			$topic_info = getTopicInfo($topic);
953
954
			// Though the topic should be there, it might have vanished.
955
			if (empty($topic_info))
956
			{
957 2
				throw new Exception('topic_doesnt_exist');
958
			}
959
960
			// Did this topic suddenly move? Just checking...
961
			if ($topic_info['id_board'] !== $board)
962
			{
963
				throw new Exception('not_a_topic');
964
			}
965
		}
966
967
		// Replying to a topic?
968
		if (!empty($topic) && !isset($_REQUEST['msg']))
969
		{
970
			// Don't allow a post if it's locked.
971
			if ($topic_info['locked'] !== 0 && !allowedTo('moderate_board'))
972 2
			{
973
				throw new Exception('topic_locked', false);
974
			}
975
976
			// Do the permissions and approval stuff...
977
			$becomesApproved = true;
978
			if ($topic_info['id_member_started'] !== $this->user->id)
979
			{
980
				if ($modSettings['postmod_active'] && allowedTo('post_unapproved_replies_any') && !allowedTo('post_reply_any'))
981 2
				{
982
					$becomesApproved = false;
983
				}
984 2
				else
985
				{
986
					isAllowedTo('post_reply_any');
987
				}
988
			}
989
			elseif (!allowedTo('post_reply_any'))
990 2
			{
991
				if ($modSettings['postmod_active'])
992
				{
993 2
					if (allowedTo('post_unapproved_replies_own') && !allowedTo('post_reply_own'))
994 2
					{
995 2
						$becomesApproved = false;
996
					}
997 2
					// Guests do not have post_unapproved_replies_own permission, so it's always post_unapproved_replies_any
998
					elseif ($this->user->is_guest && allowedTo('post_unapproved_replies_any'))
999 2
					{
1000 2
						$becomesApproved = false;
1001
					}
1002
					else
1003
					{
1004
						isAllowedTo('post_reply_own');
1005 6
					}
1006
				}
1007 6
			}
1008 6
1009
			// Normalize lock/sticky using locals without mutating $_POST
1010
			$lock = $req->hasPost('lock') ? $this->_checkLocked($req->getPost('lock', 'intval'), $topic_info) : null;
1011
1012 6
			// So you wanna (un)sticky this...let's see.
1013
			$sticky = $req->getPost('sticky', 'intval');
1014
			if ($sticky === $topic_info['is_sticky'] || !allowedTo('make_sticky'))
1015
			{
1016
				$sticky = null;
1017
			}
1018
1019
			// Trigger the save_replying event
1020
			$this->_events->trigger('save_replying', ['topic_info' => &$topic_info]);
1021
1022
			// If the number of replies has changed, if the setting is enabled, go back to action_post() - which handles the error.
1023
			if (empty($options['no_new_reply_warning']) && isset($_POST['last_msg']) && $topic_info['id_last_msg'] > $_POST['last_msg'])
1024
			{
1025
				$context['scroll_to_top'] = true;
1026
1027
				return $this->action_post();
1028
			}
1029
1030
			$posterIsGuest = $this->user->is_guest;
1031
		}
1032
		// Posting a new topic.
1033
		elseif (empty($topic))
1034
		{
1035
			// Now don't be silly, new topics will get their own id_msg soon enough.
1036
			unset($_REQUEST['msg'], $_GET['msg']);
1037
1038
			// Do like, the permissions, for safety and stuff...
1039
			$becomesApproved = true;
1040
			if ($modSettings['postmod_active'] && !allowedTo('post_new') && allowedTo('post_unapproved_topics'))
1041
			{
1042
				$becomesApproved = false;
1043
			}
1044
			else
1045
			{
1046
				isAllowedTo('post_new');
1047
			}
1048
1049
			// Trigger the save new topic event
1050
			$this->_events->trigger('save_new_topic', ['becomesApproved' => &$becomesApproved]);
1051
1052
			$lock = $req->hasPost('lock') ? $this->_checkLocked($req->getPost('lock', 'intval')) : null;
1053 6
1054
			$sticky = $req->getPost('sticky', 'intval');
1055
			if ($sticky !== null && (empty($sticky) || !allowedTo('make_sticky')))
1056
			{
1057
				$sticky = null;
1058
			}
1059
1060
			$posterIsGuest = $this->user->is_guest;
1061 6
		}
1062
		// Modifying an existing message?
1063
		elseif (isset($_REQUEST['msg']) && !empty($topic))
1064
		{
1065
			$_REQUEST['msg'] = (int) $_REQUEST['msg'];
1066 6
1067
			$msgInfo = basicMessageInfo($_REQUEST['msg'], true, false, false);
1068
1069
			if (empty($msgInfo))
1070 6
			{
1071
				throw new Exception('cant_find_messages', false);
1072
			}
1073
1074
			// Trigger teh save_modify event
1075
			$this->_events->trigger('save_modify', ['msgInfo' => &$msgInfo]);
1076
1077 6
			if (!empty($topic_info['locked']) && !allowedTo('moderate_board'))
1078
			{
1079
				throw new Exception('topic_locked', false);
1080 6
			}
1081
1082
			$lock = $req->hasPost('lock') ? $this->_checkLocked($req->getPost('lock', 'intval'), $topic_info) : null;
1083
1084
			// Change the sticky status of this topic?
1085 6
			$sticky = $req->getPost('sticky', 'intval');
1086
			if ($sticky === $topic_info['is_sticky'] || !allowedTo('make_sticky'))
1087 6
			{
1088
				$sticky = null;
1089
			}
1090 6
1091
			if ($msgInfo['id_member'] === $this->user->id && !allowedTo('modify_any'))
1092
			{
1093
				if ((!$modSettings['postmod_active'] || $msgInfo['approved']) && !empty($modSettings['edit_disable_time']) && $msgInfo['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time())
1094
				{
1095
					throw new Exception('modify_post_time_passed', false);
1096 6
				}
1097
				if ($topic_info['id_member_started'] === $this->user->id && !allowedTo('modify_own'))
1098
				{
1099
					isAllowedTo('modify_replies');
1100
				}
1101
				else
1102
				{
1103
					isAllowedTo('modify_own');
1104
				}
1105
			}
1106 6
			elseif ($topic_info['id_member_started'] === $this->user->id && !allowedTo('modify_any'))
1107
			{
1108 4
				isAllowedTo('modify_replies');
1109 4
1110
				// If you're modifying a reply, I say it better be logged...
1111
				$moderationAction = true;
1112
			}
1113 6
			else
1114
			{
1115
				isAllowedTo('modify_any');
1116
1117
				// Log it, assuming you're not modifying your own post.
1118
				if ($msgInfo['id_member'] !== $this->user->id)
1119
				{
1120
					$moderationAction = true;
1121
				}
1122
			}
1123
1124
			$posterIsGuest = empty($msgInfo['id_member']);
1125
1126
			// Can they approve it?
1127
			$can_approve = allowedTo('approve_posts');
1128
			$becomesApproved = $modSettings['postmod_active'] ? ($can_approve && !$msgInfo['approved'] ? (empty($_REQUEST['approve']) ? 0 : 1) : $msgInfo['approved']) : 1;
1129
			$approve_has_changed = $msgInfo['approved'] !== $becomesApproved;
1130
1131
			if (!allowedTo('moderate_forum') || !$posterIsGuest)
1132 6
			{
1133
				$_POST['guestname'] = $msgInfo['poster_name'];
1134
				$_POST['email'] = $msgInfo['poster_email'];
1135
			}
1136
		}
1137
1138
		// In case we want to override
1139
		if (!isset($_REQUEST['from_qr']) && allowedTo('approve_posts'))
1140
		{
1141
			$becomesApproved = !isset($_REQUEST['approve']) || !empty($_REQUEST['approve']) ? 1 : 0;
1142
			$approve_has_changed = isset($msgInfo['approved']) && $msgInfo['approved'] !== $becomesApproved;
1143
		}
1144
1145
		// If the poster is a guest, evaluate the legality of name and email.
1146 6
		// Extract common inputs up-front as locals
1147
		$guestname = $req->getPost('guestname', 'trim|Util::htmlspecialchars', '');
1148 4
		$email = $req->getPost('email', 'trim|Util::htmlspecialchars', '');
1149
		$subject = $req->getPost('subject', 'trim');
1150
		$message = $req->getPost('message', 'trim');
1151
1152 6
		if ($posterIsGuest)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $posterIsGuest does not seem to be defined for all execution paths leading up to this point.
Loading history...
1153 6
		{
1154
			if ($guestname === '' || $guestname === '_')
1155
			{
1156 6
				$this->_post_errors->addError('no_name');
1157 6
			}
1158 6
1159
			if (Util::strlen($guestname) > 25)
1160
			{
1161 6
				$this->_post_errors->addError('long_name');
1162
			}
1163
1164
			if (empty($modSettings['guest_post_no_email']))
1165
			{
1166
				// Only check if they changed it!
1167 6
				$validationArray = ['email' => $email ?? ''];
1168
				if ((!isset($msgInfo) || $msgInfo['poster_email'] !== $email) && (!allowedTo('moderate_forum') && !DataValidator::is_valid($validationArray, ['email' => 'valid_email|required'], ['email' => 'trim'])))
1169
				{
1170
					empty($email) ? $this->_post_errors->addError('no_email') : $this->_post_errors->addError('bad_email');
1171 6
				}
1172 6
1173 6
				// Now make sure this email address is not banned from posting.
1174 6
				isBannedEmail($email, 'cannot_post', sprintf($txt['you_are_post_banned'], $txt['guest_title']));
1175 6
			}
1176 6
1177
			// In case they are making multiple posts this visit, help them along by storing their name.
1178
			if (!$this->_post_errors->hasErrors())
1179
			{
1180 6
				$_SESSION['guest_name'] = $guestname;
1181 6
				$_SESSION['guest_email'] = $email;
1182 6
			}
1183 6
		}
1184
1185 6
		// Trigger before_save_post event
1186
		try
1187
		{
1188
			$this->_events->trigger('before_save_post', ['post_errors' => $this->_post_errors, 'topic_info' => $topic_info]);
1189 6
		}
1190 6
		catch (ControllerRedirectException $controllerRedirectException)
1191 6
		{
1192 6
			return $controllerRedirectException->doRedirect($this);
1193
		}
1194
1195
		// Check the subject and message.
1196 6
		if (!isset($subject) || Util::htmltrim(Util::htmlspecialchars($subject)) === '')
1197
		{
1198
			$this->_post_errors->addError('no_subject');
1199 6
		}
1200
1201
		if (!isset($message) || Util::htmltrim(Util::htmlspecialchars($message, ENT_QUOTES)) === '')
1202 2
		{
1203
			$this->_post_errors->addError('no_message');
1204
		}
1205
		elseif (!empty($modSettings['max_messageLength']) && Util::strlen($message) > $modSettings['max_messageLength'])
1206
		{
1207
			$this->_post_errors->addError(['long_message', [$modSettings['max_messageLength']]]);
1208
		}
1209 2
		else
1210
		{
1211 2
			// Prepare the message a bit for some additional testing.
1212
			$message = Util::htmlspecialchars($message, ENT_QUOTES, 'UTF-8', true);
1213
1214 2
			// Preparse code. (Zef)
1215
			if ($this->user->is_guest)
1216
			{
1217
				$this->user->name = $guestname;
1218
			}
1219
1220
			$this->preparse->preparsecode($message);
1221 4
1222
			$bbc_parser = ParserWrapper::instance();
1223
1224
			// Let's see if there's still some content left without the tags.
1225
			if (Util::htmltrim(strip_tags($bbc_parser->parseMessage($message, false), '<img>')) === '' && (!allowedTo('admin_forum') || !str_contains($message, '[html]')))
1226
			{
1227
				$this->_post_errors->addError('no_message');
1228
			}
1229
		}
1230
1231
		if ($posterIsGuest)
1232 4
		{
1233
			// If a user is a guest, make sure the chosen name isn't taken.
1234 4
			require_once(SUBSDIR . '/Members.subs.php');
1235
			if (isReservedName($guestname, 0, true, false) && (!isset($msgInfo['poster_name']) || $guestname !== $msgInfo['poster_name']))
1236 4
			{
1237
				$this->_post_errors->addError('bad_name');
1238
			}
1239
		}
1240
		// If the user isn't a guest, get his or her name and email.
1241 6
		elseif (!isset($_REQUEST['msg']))
1242
		{
1243
			$guestname = $this->user->username;
1244
			$email = $this->user->email;
1245 6
		}
1246
1247 6
		// Posting somewhere else? Are we sure you can?
1248
		if (!empty($_REQUEST['post_in_board']))
1249
		{
1250 6
			$new_board = $this->_req->getRequest('post_in_board', 'intval');
1251
			if (!allowedTo('post_new', $new_board))
1252
			{
1253
				$post_in_board = boardInfo($new_board);
1254
1255 6
				if (!empty($post_in_board))
1256
				{
1257
					$this->_post_errors->addError(['post_new_board', [$post_in_board['name']]]);
1258
				}
1259
				else
1260
				{
1261
					$this->_post_errors->addError('post_new');
1262 6
				}
1263
			}
1264
		}
1265
1266 6
		// Any mistakes?
1267
		if ($this->_post_errors->hasErrors())
1268 4
		{
1269
			$context['scroll_to_top'] = true;
1270
1271
			$_REQUEST['preview'] = false;
1272 6
1273
			return $this->action_post();
1274
		}
1275
1276
		// Make sure the user isn't spamming the board.
1277 6
		if (!isset($_REQUEST['msg']))
1278
		{
1279 2
			spamProtection('post');
1280
		}
1281
1282 6
		// At about this point, we're posting and that's that.
1283
		ignore_user_abort(true);
1284
		detectServer()->setTimeLimit(300);
1285
1286
		// Add special HTML entities to the subject, name, and email.
1287
		$subject = strtr(Util::htmlspecialchars($subject), ["\r" => '', "\n" => '', "\t" => '']);
1288 6
		$guestname = htmlspecialchars($guestname ?? '', ENT_COMPAT, 'UTF-8');
1289
		$email = htmlspecialchars($email ?? '', ENT_COMPAT, 'UTF-8');
1290 6
1291 6
		// At this point, we want to make sure the subject isn't too long.
1292
		if (Util::strlen($subject) > 100)
1293
		{
1294 2
			$subject = Util::substr($subject, 0, 100);
1295 2
		}
1296 2
1297 2
		// Creating a new topic?
1298 2
		$newTopic = empty($_REQUEST['msg']) && empty($topic);
1299 2
1300 2
		// Collect all parameters for the creation or modification of a post.
1301 2
		$msgOptions = [
1302
			'id' => $this->_req->getRequest('msg', 'intval', 0),
1303 2
			'subject' => $subject !== null ? trim($subject) : '',
1304
			'body' => $message !== null ? trim($message) : '',
1305 4
			'icon' => preg_replace('~[./*:"\'<>]~', '', $this->_req->getPost('icon', 'trim')),
1306
			'smileys_enabled' => !$this->_req->hasPost('ns'),
1307
			'approved' => $becomesApproved,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $becomesApproved does not seem to be defined for all execution paths leading up to this point.
Loading history...
1308 2
		];
1309
1310 2
		$topicOptions = [
1311
			'id' => empty($topic) ? 0 : $topic,
1312
			'board' => $board,
1313
			'lock_mode' => $lock ?? null,
1314
			'sticky_mode' => $sticky ?? null,
1315
			'mark_as_read' => true,
1316
			'is_approved' => !$modSettings['postmod_active'] || empty($topic) || !empty($board_info['cur_topic_approved']),
1317
		];
1318
1319 6
		$posterOptions = [
1320
			'id' => $this->user->id,
1321
			'name' => $guestname ?? $this->user->name,
1322
			'email' => $email ?? ($this->user->is_guest ? '' : $this->user->email),
1323
			'update_post_count' => $this->user->is_guest === false && !isset($_REQUEST['msg']) && $board_info['posts_count'],
1324 6
		];
1325
1326
		// Trigger the pre_save_post event
1327
		$this->_events->trigger('pre_save_post', ['msgOptions' => &$msgOptions, 'topicOptions' => &$topicOptions, 'posterOptions' => &$posterOptions]);
1328
1329 6
		// This is an already existing message. Edit it.
1330
		if (!empty($_REQUEST['msg']))
1331
		{
1332
			$posterOptions['id_starter'] = $msgInfo['id_member'] ?? $this->user->id;
1333
1334
			// Have admins allowed people to hide their screwups?
1335 6
			if (time() - $msgInfo['poster_time'] > $modSettings['edit_wait_time'] || $this->user->id !== $msgInfo['id_member'])
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $msgInfo does not seem to be defined for all execution paths leading up to this point.
Loading history...
1336
			{
1337
				$msgOptions['modify_time'] = time();
1338
				$msgOptions['modify_name'] = $this->user->name;
1339 6
			}
1340
1341
			// This will save some time...
1342
			if (empty($approve_has_changed))
1343
			{
1344
				unset($msgOptions['approved']);
1345
			}
1346 6
1347
			modifyPost($msgOptions, $topicOptions, $posterOptions);
1348 6
		}
1349
		// This is a new topic or an already existing one. Save it.
1350
		else
1351
		{
1352
			// We also have to fake the board:
1353
			// if it's valid, and it's not the current, let's forget about the "current" and load the new one
1354
			if (!empty($new_board) && $board !== $new_board)
1355
			{
1356
				$board = $new_board;
1357
				loadBoard();
1358 2
1359
				// Some details changed
1360
				$topicOptions['board'] = $board;
1361 2
				$topicOptions['is_approved'] = !$modSettings['postmod_active'] || empty($topic) || !empty($board_info['cur_topic_approved']);
1362
				$posterOptions['update_post_count'] = $this->user->is_guest === false && !isset($_REQUEST['msg']) && $board_info['posts_count'];
1363
			}
1364
1365
			createPost($msgOptions, $topicOptions, $posterOptions);
1366
1367
			$topic = $topicOptions['id'];
1368
		}
1369
1370
		// Trigger the after_save_post event
1371
		$this->_events->trigger('after_save_post', ['board' => $board, 'topic' => $topic, 'msgOptions' => $msgOptions, 'topicOptions' => $topicOptions, 'becomesApproved' => $becomesApproved, 'posterOptions' => $posterOptions]);
1372
1373
		// Marking boards as read.
1374
		// (You just posted, and they will be unread.)
1375
		if ($this->user->is_guest === false)
1376
		{
1377
			$board_list = empty($board_info['parent_boards']) ? [] : array_keys($board_info['parent_boards']);
1378
1379
			// Returning to the topic?
1380
			if (!empty($_REQUEST['goback']))
1381 2
			{
1382
				$board_list[] = $board;
1383
			}
1384
1385
			if (!empty($board_list))
1386 2
			{
1387
				markBoardsRead($board_list);
1388
			}
1389
		}
1390
1391 2
		// Turn notification on or off.
1392
		if ($req->getPost('notify') && allowedTo('mark_any_notify'))
1393
		{
1394
			setTopicNotification($this->user->id, $topic, true);
1395
		}
1396
		elseif (!$newTopic)
1397
		{
1398
			setTopicNotification($this->user->id, $topic);
1399
		}
1400
1401
		// Log an act of moderation - modifying.
1402
		if (!empty($moderationAction))
1403
		{
1404
			logAction('modify', ['topic' => $topic, 'message' => (int) $_REQUEST['msg'], 'member' => $msgInfo['id_member'], 'board' => $board]);
1405
		}
1406
1407 2
		if (isset($lock) && $lock !== 2)
1408
		{
1409
			logAction(empty($lock) ? 'unlock' : 'lock', ['topic' => $topicOptions['id'], 'board' => $topicOptions['board']]);
1410 2
		}
1411
1412
		if (isset($sticky))
1413
		{
1414
			logAction(empty($sticky) ? 'unsticky' : 'sticky', ['topic' => $topicOptions['id'], 'board' => $topicOptions['board']]);
1415
		}
1416
1417
		// Notify any members who have notification turned on for this topic/board - only do this if it's going to be approved(!)
1418
		if ($becomesApproved)
1419
		{
1420
			require_once(SUBSDIR . '/Notification.subs.php');
1421
			if ($newTopic)
1422
			{
1423
				$notifyData = [
1424
					'body' => $msgOptions['body'],
1425
					'subject' => $msgOptions['subject'],
1426
					'name' => $this->user->name,
1427
					'poster' => $this->user->id,
1428
					'msg' => $msgOptions['id'],
1429
					'board' => $board,
1430
					'topic' => $topic,
1431
					'signature' => User::$settings->signature(''),
1432
				];
1433
				sendBoardNotifications($notifyData);
1434
			}
1435
			elseif (empty($_REQUEST['msg']))
1436
			{
1437
				// Only send it to everyone if the topic is approved, otherwise just to the topic starter if they want it.
1438
				if ($topic_info['approved'])
1439
				{
1440
					sendNotifications($topic, 'reply');
1441
				}
1442
				else
1443
				{
1444
					sendNotifications($topic, 'reply', [], $topic_info['id_member_started']);
1445
				}
1446
			}
1447
		}
1448
1449
		if ($board_info['num_topics'] === 0)
1450
		{
1451
			Cache::instance()->remove('board-' . $board);
1452
		}
1453
1454
		if ($req->getPost('announce_topic'))
1455
		{
1456
			redirectexit('action=announce;sa=selectgroup;topic=' . $topic . ($req->hasPost('move') && allowedTo('move_any') ? ';move' : '') . (empty($_REQUEST['goback']) ? '' : ';goback'));
1457
		}
1458
1459
		if ($req->getPost('move') && allowedTo('move_any'))
1460
		{
1461
			redirectexit('action=movetopic;topic=' . $topic . '.0' . (empty($_REQUEST['goback']) ? '' : ';goback'));
1462
		}
1463
1464
		// Return to post if the mod is on.
1465
		if (isset($_REQUEST['msg']) && !empty($_REQUEST['goback']))
1466
		{
1467
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
1468
		}
1469
		elseif (!empty($_REQUEST['goback']))
1470
		{
1471
			redirectexit('topic=' . $topic . '.new#new');
1472
		}
1473
		// Dut-dut-duh-duh-DUH-duh-dut-duh-duh!  *dances to the Final Fantasy Fanfare...*
1474
		else
1475
		{
1476
			redirectexit('board=' . $board . '.0');
1477
		}
1478
	}
1479
1480
	/**
1481
	 * Toggle a post-lock status
1482
	 *
1483
	 * @param int $lock
1484
	 * @param string|null $topic_info
1485
	 *
1486
	 * @return int|null
1487
	 */
1488
	protected function _checkLocked($lock, $topic_info = null): ?int
1489
	{
1490
		// A new topic
1491
		if ($topic_info === null)
1492
		{
1493
			// New topics are by default not locked.
1494
			if (empty($lock))
1495
			{
1496
				return null;
1497
			}
1498
			// Besides, you need permission.
1499
			if (!allowedTo(['lock_any', 'lock_own']))
1500
			{
1501
				return null;
1502
			}
1503
			// A moderator-lock (1) can override a user-lock (2).
1504
			return allowedTo('lock_any') ? 1 : 2;
1505
		}
1506
		// Nothing changes to the lock status.
1507
		if ((empty($lock) && empty($topic_info['locked'])) || (!empty($lock) && !empty($topic_info['locked'])))
1508
		{
1509
			return null;
1510
		}
1511
		// You're simply not allowed to (un)lock this.
1512
		if (!allowedTo(['lock_any', 'lock_own']) || (!allowedTo('lock_any') && $this->user->id !== $topic_info['id_member_started']))
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1513
		{
1514
			return null;
1515
		}
1516
		// You're only allowed to lock your own topics.
1517
		if (!allowedTo('lock_any'))
1518
		{
1519
			// You're not allowed to break a moderator's lock.
1520
			if ((int) $topic_info['locked'] === 1)
1521
			{
1522
				return null;
1523
			}
1524
			$lock = empty($lock) ? 0 : 2;
1525
		}
1526
		// You must be the moderator.
1527
		else
1528
		{
1529
			$lock = empty($lock) ? 0 : 1;
1530
		}
1531
1532
		return $lock;
1533
	}
1534
1535
	/**
1536
	 * Loads a post and inserts it into the current editing text box.
1537
	 * Used to "quick edit" a post as well as to quote a post and place it in the quick reply box
1538
	 * Can be used to "quick edit" just the subject from the topic listing
1539
	 *
1540
	 * Uses the Post language file.
1541
	 * Uses special (sadly browser-dependent) JavaScript to parse entities for internationalization reasons.
1542
	 * Accessed with ?action=quotefast and ?action=quotefast;modify
1543
	 */
1544
	public function action_quotefast(): void
1545
	{
1546
		global $context;
1547
1548
		Txt::load('Post');
1549
1550
		// Where we going if we need to?
1551
		$context['post_box_name'] = $_GET['pb'] ?? '';
1552
1553
		$row = quoteMessageInfo((int) $_REQUEST['quote'], isset($_REQUEST['modify']));
1554
1555
		$context['sub_template'] = 'quotefast';
1556
		if (!empty($row))
1557
		{
1558
			$can_view_post = $row['approved'] || ($row['id_member'] !== 0 && $row['id_member'] === $this->user->id) || allowedTo('approve_posts', $row['id_board']);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1559
		}
1560
1561
		if (!empty($can_view_post))
1562
		{
1563
			// Remove special formatting we don't want anymore.
1564
			$row['body'] = $this->preparse->un_preparsecode($row['body']);
1565
1566
			// Censor the message!
1567
			$row['body'] = censor($row['body']);
1568
1569
			$row['body'] = preg_replace('~<br ?/?>~i', "\n", $row['body']);
1570
1571
			// Want to modify a single message by double-clicking it?
1572
			if (isset($_REQUEST['modify']))
1573
			{
1574
				$row['subject'] = censor($row['subject']);
1575
1576
				$context['sub_template'] = 'modifyfast';
1577
				$context['message'] = [
1578
					'id' => $_REQUEST['quote'],
1579
					'body' => $row['body'],
1580
					'subject' => addcslashes($row['subject'], '"'),
1581
				];
1582
1583
				return;
1584
			}
1585
1586
			// Remove any nested quotes.
1587
			$row['body'] = removeNestedQuotes($row['body']);
1588
1589
			// Add a quote string on the front and end.
1590
			$context['quote']['xml'] = '[quote author=' . $row['poster_name'] . ' link=msg=' . (int) $_REQUEST['quote'] . ' date=' . $row['poster_time'] . "]\n" . $row['body'] . "\n[/quote]";
1591
			$context['quote']['text'] = strtr(un_htmlspecialchars($context['quote']['xml']), ["'" => '\\\'', '\\' => '\\\\', "\n" => '\\n', '</script>' => "</' + 'script>"]);
1592
			$context['quote']['xml'] = strtr($context['quote']['xml'], ['&nbsp;' => '&#160;', '<' => '&lt;', '>' => '&gt;']);
1593
		}
1594
		//@todo Needs a nicer interface.
1595
		// In case our message has been removed in the meantime.
1596
		elseif (isset($_REQUEST['modify']))
1597
		{
1598
			$context['sub_template'] = 'modifyfast';
1599
			$context['message'] = [
1600
				'id' => 0,
1601
				'body' => '',
1602
				'subject' => '',
1603
			];
1604
		}
1605
		else
1606
		{
1607
			$context['quote'] = [
1608
				'xml' => '',
1609
				'text' => '',
1610
			];
1611
		}
1612
	}
1613
1614
	/**
1615
	 * Used to edit the body or subject of a message inline
1616
	 * called from action=jsmodify from script and topic js
1617
	 */
1618
	public function action_jsmodify(): void
1619
	{
1620
		global $modSettings, $board, $topic, $context;
1621
1622
		// We have to have a topic!
1623
		if (empty($topic))
1624
		{
1625
			obExit(false);
1626
		}
1627
1628
		checkSession('request');
1629
1630
		$row = getTopicInfoByMsg($topic, empty($_REQUEST['msg']) ? 0 : (int) $_REQUEST['msg']);
1631
1632
		if (empty($row))
1633
		{
1634
			throw new Exception('no_board', false);
1635
		}
1636
1637
		// Change either body or subject requires permissions to modify messages.
1638
		if (isset($_POST['message']) || isset($_POST['subject']) || isset($_REQUEST['icon']))
1639
		{
1640
			if (!empty($row['locked']))
1641
			{
1642
				isAllowedTo('moderate_board');
1643
			}
1644
1645
			if ($row['id_member'] === $this->user->id && !allowedTo('modify_any'))
1646
			{
1647
				if ((!$modSettings['postmod_active'] || $row['approved']) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time())
1648
				{
1649
					throw new Exception('modify_post_time_passed', false);
1650
				}
1651
				if ($row['id_member_started'] === $this->user->id && !allowedTo('modify_own'))
1652
				{
1653
					isAllowedTo('modify_replies');
1654
				}
1655
				else
1656
				{
1657
					isAllowedTo('modify_own');
1658
				}
1659
			}
1660
			elseif ($row['id_member_started'] === $this->user->id && !allowedTo('modify_any'))
1661
			{
1662
				isAllowedTo('modify_replies');
1663
			}
1664
			else
1665
			{
1666
				isAllowedTo('modify_any');
1667
			}
1668
1669
			// Only log this action if it wasn't your message.
1670
			$moderationAction = $row['id_member'] !== $this->user->id;
1671
		}
1672
1673
		if (isset($_POST['subject']) && Util::htmltrim(Util::htmlspecialchars($_POST['subject'])) !== '')
1674
		{
1675
			$_POST['subject'] = strtr(Util::htmlspecialchars($_POST['subject']), ["\r" => '', "\n" => '', "\t" => '']);
1676
1677
			// Maximum number of characters.
1678
			if (Util::strlen($_POST['subject']) > 100)
1679
			{
1680
				$_POST['subject'] = Util::substr($_POST['subject'], 0, 100);
1681
			}
1682
		}
1683
		elseif (isset($_POST['subject']))
1684
		{
1685
			$this->_post_errors->addError('no_subject');
1686
			unset($_POST['subject']);
1687
		}
1688
1689
		if (isset($_POST['message']))
1690
		{
1691
			if (Util::htmltrim(Util::htmlspecialchars($_POST['message'])) === '')
1692
			{
1693
				$this->_post_errors->addError('no_message');
1694
				unset($_POST['message']);
1695
			}
1696
			elseif (!empty($modSettings['max_messageLength']) && Util::strlen($_POST['message']) > $modSettings['max_messageLength'])
1697
			{
1698
				$this->_post_errors->addError(['long_message', [$modSettings['max_messageLength']]]);
1699
				unset($_POST['message']);
1700
			}
1701
			else
1702
			{
1703
				$_POST['message'] = Util::htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8', true);
1704
1705
				$this->preparse->preparsecode($_POST['message']);
1706
				$bbc_parser = ParserWrapper::instance();
1707
1708
				if (Util::htmltrim(strip_tags($bbc_parser->parseMessage($_POST['message'], false), '<img>')) === '')
1709
				{
1710
					$this->_post_errors->addError('no_message');
1711
					unset($_POST['message']);
1712
				}
1713
			}
1714
		}
1715
1716
		if (isset($_POST['lock']))
1717
		{
1718
			$_POST['lock'] = $this->_checkLocked($_POST['lock'], $row);
1719
		}
1720
1721
		if (isset($_POST['sticky']) && !allowedTo('make_sticky'))
1722
		{
1723
			unset($_POST['sticky']);
1724
		}
1725
1726
		if (!$this->_post_errors->hasErrors())
1727
		{
1728
			if (!empty($modSettings['mentions_enabled']))
1729
			{
1730
				if (!empty($_REQUEST['uid']))
1731
				{
1732
					$query_params = [];
1733
					$query_params['member_ids'] = array_unique(array_map('intval', $_REQUEST['uid']));
1734
1735
					require_once(SUBSDIR . '/Members.subs.php');
1736
					$mentioned_members = membersBy('member_ids', $query_params, true);
1737
					$replacements = 0;
1738
					$actually_mentioned = [];
1739
					foreach ($mentioned_members as $member)
1740
					{
1741
						$_POST['message'] = str_replace('@' . $member['real_name'], '[member=' . $member['id_member'] . ']' . $member['real_name'] . '[/member]', $_POST['message'], $replacements);
1742
						if ($replacements > 0)
1743
						{
1744
							$actually_mentioned[] = $member['id_member'];
1745
						}
1746
					}
1747
				}
1748
1749
				if (!empty($actually_mentioned))
1750
				{
1751
					$notifier = Notifications::instance();
1752
					$notifier->add(new NotificationsTask(
1753
						'Mentionmem',
1754
						$row['id_msg'],
1755
						$row['id_member'],
1756
						['id_members' => $actually_mentioned, 'status' => $row['approved'] ? 'new' : 'unapproved']
1757
					));
1758
				}
1759
			}
1760
1761
			$msgOptions = [
1762
				'id' => $row['id_msg'],
1763
				'subject' => $_POST['subject'] ?? null,
1764
				'body' => $_POST['message'] ?? null,
1765
				'icon' => isset($_REQUEST['icon']) ? preg_replace('~[\./\\\\*\':"<>]~', '', $_REQUEST['icon']) : null,
1766
			];
1767
1768
			$topicOptions = [
1769
				'id' => $topic,
1770
				'board' => $board,
1771
				'lock_mode' => isset($_POST['lock']) ? (int) $_POST['lock'] : null,
1772
				'sticky_mode' => isset($_POST['sticky']) ? (int) $_POST['sticky'] : null,
1773
				'mark_as_read' => false,
1774
			];
1775
1776
			$posterOptions = [];
1777
1778
			// Only consider marking as editing if they have edited the subject, message or icon.
1779
			if ((isset($_POST['subject']) && $_POST['subject'] !== $row['subject']) || (isset($_POST['message']) && $_POST['message'] !== $row['body']) || (isset($_REQUEST['icon']) && $_REQUEST['icon'] !== $row['icon']))
1780
			{
1781
				// And even then only if the time has passed...
1782
				if (time() - $row['poster_time'] > $modSettings['edit_wait_time'] || $this->user->id !== $row['id_member'])
1783
				{
1784
					$msgOptions['modify_time'] = time();
1785
					$msgOptions['modify_name'] = $this->user->name;
1786
				}
1787
			}
1788
			// If nothing was changed, there's no need to add an entry to the moderation log.
1789
			else
1790
			{
1791
				$moderationAction = false;
1792
			}
1793
1794
			modifyPost($msgOptions, $topicOptions, $posterOptions);
1795
1796
			// If we didn't change anything this time but had before put back the old info.
1797
			if (!isset($msgOptions['modify_time']) && !empty($row['modified_time']))
1798
			{
1799
				$msgOptions['modify_time'] = $row['modified_time'];
1800
				$msgOptions['modify_name'] = $row['modified_name'];
1801
			}
1802
1803
			// Changing the first subject updates other subjects to 'Re: new_subject'.
1804
			if (isset($_POST['subject'], $_REQUEST['change_all_subjects']) && $row['id_first_msg'] === $row['id_msg'] && !empty($row['num_replies']) && (allowedTo('modify_any') || ($row['id_member_started'] === $this->user->id && allowedTo('modify_replies'))))
1805
			{
1806
				// Get the proper (default language) response prefix first.
1807
				$context['response_prefix'] = response_prefix();
1808
1809
				topicSubject(['id_topic' => $topic, 'id_first_msg' => $row['id_first_msg']], $_POST['subject'], $context['response_prefix'], true);
1810
			}
1811
1812
			if (!empty($moderationAction))
1813
			{
1814
				logAction('modify', ['topic' => $topic, 'message' => $row['id_msg'], 'member' => $row['id_member'], 'board' => $board]);
1815
			}
1816
		}
1817
1818
		if ($this->getApi() === 'xml')
1819
		{
1820
			$bbc_parser = ParserWrapper::instance();
1821
1822
			theme()->getTemplates()->load('Xml');
1823
			theme()->getLayers()->removeAll();
1824
			$context['sub_template'] = 'modifydone';
1825
1826
			if (isset($msgOptions['subject'], $msgOptions['body']) && !$this->_post_errors->hasErrors())
1827
			{
1828
				$context['message'] = [
1829
					'id' => $row['id_msg'],
1830
					'modified' => [
1831
						'time' => isset($msgOptions['modify_time']) ? standardTime($msgOptions['modify_time']) : '',
1832
						'html_time' => isset($msgOptions['modify_time']) ? htmlTime($msgOptions['modify_time']) : '',
1833
						'timestamp' => isset($msgOptions['modify_time']) ? forum_time(true, $msgOptions['modify_time']) : 0,
1834
						'name' => isset($msgOptions['modify_time']) ? $msgOptions['modify_name'] : '',
1835
					],
1836
					'subject' => $msgOptions['subject'],
1837
					'first_in_topic' => $row['id_msg'] === $row['id_first_msg'],
1838
					'body' => strtr($msgOptions['body'], [']]>' => ']]]]><![CDATA[>']),
1839
				];
1840
1841
				$context['message']['subject'] = censor($context['message']['subject']);
1842
				$context['message']['body'] = censor($context['message']['body']);
1843
1844
				$context['message']['body'] = $bbc_parser->parseMessage($context['message']['body'], $row['smileys_enabled']);
1845
			}
1846
			// Topic?
1847
			elseif (!$this->_post_errors->hasErrors())
1848
			{
1849
				$context['sub_template'] = 'modifytopicdone';
1850
				$context['message'] = [
1851
					'id' => $row['id_msg'],
1852
					'modified' => [
1853
						'time' => isset($msgOptions['modify_time']) ? standardTime($msgOptions['modify_time']) : '',
1854
						'html_time' => isset($msgOptions['modify_time']) ? htmlTime($msgOptions['modify_time']) : '',
1855
						'timestamp' => isset($msgOptions['modify_time']) ? forum_time(true, $msgOptions['modify_time']) : 0,
1856
						'name' => isset($msgOptions['modify_time']) ? $msgOptions['modify_name'] : '',
1857
					],
1858
					'subject' => $msgOptions['subject'] ?? '',
1859
				];
1860
1861
				$context['message']['subject'] = censor($context['message']['subject']);
1862
			}
1863
			else
1864
			{
1865
				$context['message'] = [
1866
					'id' => $row['id_msg'],
1867
					'error_in_subject' => $this->_post_errors->hasError('no_subject'),
1868
					'error_in_body' => $this->_post_errors->hasError('no_message') || $this->_post_errors->hasError('long_message'),
1869
				];
1870
				$context['message']['errors'] = $this->_post_errors->prepareErrors();
1871
			}
1872
		}
1873
		else
1874
		{
1875
			obExit(false);
1876
		}
1877
	}
1878
}
1879