Display::trackStats()   A
last analyzed

Complexity

Conditions 6
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 7
nc 2
nop 1
dl 0
loc 13
ccs 0
cts 0
cp 0
crap 42
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This controls topic display, with all related functions, its is the forum
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
namespace ElkArte\Controller;
18
19
use ElkArte\AbstractController;
20
use ElkArte\Exceptions\Exception;
21
use ElkArte\Helper\Util;
22
use ElkArte\Helper\ValuesContainer;
23
use ElkArte\MembersList;
24
use ElkArte\MessagesCallback\BodyParser\Normal;
25
use ElkArte\MessagesCallback\DisplayRenderer;
26
use ElkArte\MessagesDelete;
27
use ElkArte\MessageTopicIcons;
28
use ElkArte\User;
29
30
/**
31
 * This controller is the most important and probably most accessed of all.
32
 * It controls topic display, with all related.
33
 */
34
class Display extends AbstractController
35
{
36
	/** @var null|object The template layers object */
37
	protected $_template_layers;
38
39
	/** @var int The message id when in the form msg123 */
40
	protected $_virtual_msg = 0;
41
42
	/** @var int Show signatures? */
43
	protected $_show_signatures = 0;
44
45
	/** @var int|string Start viewing the topics from ... (page, all, other) */
46
	protected $_start;
47
48
	/** @var bool if to include unapproved posts in the count */
49
	protected $includeUnapproved;
50
51
	/** @var array data returned from getTopicInfo() */
52
	protected $topicinfo;
53
54
	/** @var int number of messages to show per topic page */
55
	protected $messages_per_page;
56
57
	/** @var int message number to start the listing from */
58
	protected $start_from;
59
60
	/**
61
	 * Default action handler for this controller, if its called directly
62
	 */
63
	public function action_index()
64
	{
65
		// what to do... display things!
66
		$this->action_display();
67
	}
68
69
	/**
70
	 * The central part of the board - topic display.
71
	 *
72
	 * What it does:
73 2
	 *
74
	 * - This function loads the posts in a topic, so they can be displayed.
75
	 * - It requires a topic, and can go to the previous or next topic from it.
76 2
	 * - It jumps to the correct post depending on a number/time/IS_MSG passed.
77 2
	 * - It depends on the messages_per_page, defaultMaxMessages and enableAllMessages settings.
78
	 * - It is accessed by ?topic=id_topic.START.
79
	 *
80
	 * @uses the main sub template of the Display template.
81
	 */
82
	public function action_display(): void
83
	{
84
		global $txt, $modSettings, $context, $settings, $options, $topic, $board;
85
		global $messages_request;
86
87
		$this->_events->trigger('pre_load', ['_REQUEST' => &$_REQUEST, 'topic' => $topic, 'board' => &$board]);
88
89
		// What are you gonna display if these are empty?!
90
		if (empty($topic))
91
		{
92 2
			throw new Exception('no_board', false);
93
		}
94 2
95 2
		// And the topic functions
96 2
		require_once(SUBSDIR . '/Topic.subs.php');
97
		require_once(SUBSDIR . '/Messages.subs.php');
98 2
99
		// link prefetch is slower for the server, and it makes it impossible to know if they read it.
100
		stop_prefetching();
101 2
102
		// How much are we sticking on each page?
103
		$this->setMessagesPerPage();
104
		$this->includeUnapproved = $this->getIncludeUnapproved();
105
		$this->_start = $this->_req->getQuery('start');
106
107 2
		// Find the previous or next topic.  Make a fuss if there are no more.
108 2
		$this->getPreviousNextTopic();
109
110
		// Add 1 to the number of views of this topic (except for robots).
111 2
		$this->increaseTopicViews($topic);
112 2
113
		// Time to load the topic information
114
		$this->loadTopicInfo($topic, $board);
115 2
116
		// Is this a moved topic that we are redirecting to or coming from?
117
		$this->handleRedirection();
118 2
119 2
		// Trigger the topicinfo event for display
120 2
		$this->_events->trigger('topicinfo', ['topicinfo' => &$this->topicinfo, 'includeUnapproved' => $this->includeUnapproved]);
121 2
122
		// If this topic has unapproved posts, we need to work out how many posts the user can see, for page indexing.
123
		$total_visible_posts = $this->getVisiblePosts($this->topicinfo['num_replies']);
124 2
125
		// The start isn't a number; it's information about what to do, where to go.
126 2
		$this->makeStartAdjustments($total_visible_posts);
127
128 2
		// Censor the subject...
129
		$this->topicinfo['subject'] = censor($this->topicinfo['subject']);
130 2
131
		// Allow addons access to the topicinfo array
132
		call_integration_hook('integrate_display_topic', [$this->topicinfo]);
133
134
		// If all is set, figure out what needs to be done
135 2
		$can_show_all = $this->getCanShowAll($total_visible_posts);
136 2
		$this->setupShowAll($can_show_all, $total_visible_posts);
137
138
		// Time to place all the particulars into context for the template
139
		$this->setMessageContext();
140
141
		// Calculate the fastest way to get the messages!
142 2
		$ascending = true;
143
		$start = $this->_start;
144
		$limit = $this->messages_per_page;
145 2
		$firstIndex = 0;
146
147
		if ($start >= $total_visible_posts / 2 && $this->messages_per_page !== -1)
148
		{
149
			$ascending = false;
150
			$limit = $total_visible_posts <= $start + $limit ? $total_visible_posts - $start : $limit;
151
			$start = $total_visible_posts <= $start + $limit ? 0 : $total_visible_posts - $start - $limit;
152
			$firstIndex = $limit - 1;
153
		}
154 2
155
		// Taking care of member specific settings
156
		$limit_settings = [
157
			'messages_per_page' => $this->messages_per_page,
158 2
			'start' => $start,
159
			'offset' => $limit,
160 2
		];
161 2
162
		// Get each post and poster on this topic page.
163
		$topic_details = getTopicsPostsAndPoster($this->topicinfo['id_topic'], $limit_settings, $ascending);
164 2
		$messages = $topic_details['messages'];
165 2
166
		// Add the viewing member so their information is available for use in enhanced QR
167 2
		$posters = array_unique($topic_details['all_posters'] + [-1 => $this->user->id]);
168 2
		$all_posters = $topic_details['all_posters'];
169 2
		unset($topic_details);
170
171
		// Default this topic to not marked for notifications... of course...
172
		$context['is_marked_notify'] = false;
173 2
174
		$messages_request = false;
175
		$context['first_message'] = 0;
176 2
		$context['first_new_message'] = false;
177 2
178
		call_integration_hook('integrate_display_message_list', [&$messages, &$all_posters]);
179
180
		// If there _are_ messages here... (probably an error otherwise :!)
181
		if (!empty($messages))
182
		{
183 2
			// Mark the board as read or not ... calls updateReadNotificationsFor() sets $context['is_marked_notify']
184
			$this->markRead($messages, $board);
185
186
			// Mark notifications for this member and first seen messages
187
			$this->markNotificationsRead($messages);
188
189 2
			$msg_parameters = [
190 2
				'message_list' => $messages,
191 2
				'new_from' => $this->topicinfo['new_from'],
192 2
			];
193 2
			$msg_selects = [];
194
			$msg_tables = [];
195
			call_integration_hook('integrate_message_query', [&$msg_selects, &$msg_tables, &$msg_parameters]);
196
197
			MembersList::loadGuest();
198
199
			// What?  It's not like it *couldn't* be only guests in this topic...
200
			MembersList::load($posters);
201
202
			// If using quick reply, load the user into context for the poster area
203
			$this->prepareQuickReply();
204
205 2
			$messages_request = loadMessageRequest($msg_selects, $msg_tables, $msg_parameters);
206 2
207
			// Go to the last message if the given time is beyond the time of the last message.
208 2
			if ($this->start_from >= $this->topicinfo['num_replies'])
209
			{
210
				$this->start_from = $this->topicinfo['num_replies'];
211 2
				$context['start_from'] = $this->start_from;
212
			}
213
214
			// Since the anchor information is needed on the top of the page we load these variables beforehand.
215
			$context['first_message'] = $messages[$firstIndex] ?? $messages[0];
216
			$context['first_new_message'] = (int) $this->_start === (int) $this->start_from;
217 2
		}
218
219
		// Are we showing the signatures?
220
		$this->setSignatureShowStatus();
221
222
		// Now set all the wonderful, wonderful permissions... like moderation ones...
223 2
		$this->setTopicCanPermissions();
224
225
		// Set the callback.  (do you REALIZE how much memory all the messages would take?!?)
226
		// This will be called from the template.
227
		$bodyParser = new Normal([], false);
228
		$opt = new ValuesContainer([
229 2
			'icon_sources' => new MessageTopicIcons(!empty($modSettings['messageIconChecks_enable']), $settings['theme_dir']),
230
			'show_signatures' => $this->_show_signatures,
231
		]);
232
		$renderer = new DisplayRenderer($messages_request, $this->user, $bodyParser, $opt);
233 2
		$context['get_message'] = [$renderer, 'getContext'];
234
235 2
		// Load up the Quick ModifyTopic and Quick Reply scripts
236 2
		loadJavascriptFile('topic.js');
237
238
		// Create the editor for the QR area
239
		$editorOptions = [
240
			'id' => 'message',
241
			'value' => '',
242
			'labels' => [
243
				'post_button' => $txt['post'],
244 2
			],
245
			// add height and width for the editor
246
			'height' => '250px',
247 2
			'width' => '100%',
248
			'smiley_container' => 'smileyBox_message',
249
			'bbc_container' => 'bbcBox_message',
250 2
			// We submit/switch to full post page for the preview
251
			'preview_type' => 1,
252
			'buttons' => [
253
				'more' => [
254
					'type' => 'submit',
255
					'name' => 'more_options',
256
					'value' => $txt['post_options'],
257
					'options' => ''
258 2
				]
259
			],
260
		];
261
262
		// Load the template basics now as template_layers is requested by the prepare_context event
263 2
		theme()->getTemplates()->load('Display');
264
		$this->_template_layers = theme()->getLayers();
265
		$this->_template_layers->addEnd('messages_informations');
266
		$context['sub_template'] = 'messages';
267
268
		// Trigger the prepare_context event for modules that have tied in to it
269
		$this->_events->trigger('prepare_context', ['editorOptions' => &$editorOptions, 'use_quick_reply' => !empty($options['display_quick_reply'])]);
270
271
		// Load up the "double post" sequencing magic.
272
		if (!empty($options['display_quick_reply']))
273
		{
274
			checkSubmitOnce('register');
275
			$context['name'] = $_SESSION['guest_name'] ?? '';
276
			$context['email'] = $_SESSION['guest_email'] ?? '';
277
			if ($context['can_reply'])
278 2
			{
279
				// Needed for the editor and message icons.
280 2
				require_once(SUBSDIR . '/Editor.subs.php');
281 2
				create_control_richedit($editorOptions);
282
			}
283
		}
284
285 2
		theme()->addJavascriptVar(['notification_topic_notice' => $context['is_marked_notify'] ? $txt['notification_disable_topic'] : $txt['notification_enable_topic']], true);
286
287 2
		// Build the common to all buttons like Reply Notify Mark ....
288
		$this->buildNormalButtons();
289
290
		// Build specialized buttons, like moderation
291
		$this->buildModerationButtons();
292
293
		// Let's get nosey, who is viewing this topic?
294
		if (!empty($settings['display_who_viewing']))
295
		{
296 2
			require_once(SUBSDIR . '/Who.subs.php');
297
			formatViewers($this->topicinfo['id_topic'], 'topic');
298
		}
299
300
		// Did we report a post to a moderator just now?
301 2
		if (isset($this->_req->query->reportsent))
302
		{
303
			$this->_template_layers->add('report_sent');
304 2
		}
305 2
306
		// All of our buttons and bottom navigation
307
		$this->_template_layers->add('moderation_buttons');
308
		$this->_template_layers->add('pages_and_buttons');
309
310 2
		// Quick reply & modify enabled?
311 2
		if ($context['can_reply'] && !empty($options['display_quick_reply']))
312
		{
313
			loadJavascriptFile(['editor/mentioning.js', 'quickQuote.js'], ['defer' => true]);
314
			$this->_template_layers->addBefore('quickreply', 'moderation_buttons');
315
			theme()->addInlineJavascript("
316
				document.addEventListener('DOMContentLoaded', () => new Elk_QuickQuote(), false);", true
317 2
			);
318 2
		}
319
	}
320
321 2
	/**
322 2
	 * Sets the message per page
323
	 */
324
	public function setMessagesPerPage(): void
325 2
	{
326
		global $modSettings, $options;
327
328 2
		$this->messages_per_page = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? (int) $options['messages_per_page'] : (int) $modSettings['defaultMaxMessages'];
329
	}
330
331 2
	/**
332 2
	 * Returns IF we are counting unapproved posts
333
	 *
334
	 * @return bool
335
	 */
336
	public function getIncludeUnapproved(): bool
337
	{
338 2
		global $modSettings;
339
340 2
		return !$modSettings['postmod_active'] || allowedTo('approve_posts');
341 2
	}
342
343
	/**
344
	 * Return if we allow showing ALL messages for a topic vs pagination
345 2
	 *
346 2
	 * @param int $total_visible_posts
347
	 * @return bool
348
	 */
349
	public function getCanShowAll($total_visible_posts): bool
350
	{
351 2
		global $modSettings;
352
353
		return !empty($modSettings['enableAllMessages'])
354
			&& $total_visible_posts > $this->messages_per_page
355
			&& $total_visible_posts < $modSettings['enableAllMessages'];
356
	}
357 2
358 2
	/**
359
	 * If show all is requested, and allowed, setup to do just that
360
	 *
361
	 * @param bool $can_show_all
362 2
	 * @param int $total_visible_posts
363 2
	 * @return void
364 2
	 */
365
	public function setupShowAll($can_show_all, $total_visible_posts): void
366
	{
367
		global $scripturl, $topic, $context, $url_format;
368
369 2
		$all_requested = $this->_req->getQuery('all', 'trim', null);
370 2
		if (isset($all_requested))
371
		{
372
			// If all is set, but not allowed... just unset it.
373
			if (!$can_show_all)
374 2
			{
375
				unset($all_requested);
376
			}
377
			else
378
			{
379
				// Otherwise, it must be allowed... so pretend start was -1.
380
				$this->_start = -1;
381
			}
382
		}
383
384 2
		// Construct the page index, allowing for the .START method...
385 2
		if ($url_format === 'queryless')
386 2
		{
387
			$context['page_index'] = constructPageIndex($scripturl . '?topic,' . $topic . '.%1$d.html', $this->_start, $total_visible_posts, $this->messages_per_page, true, ['all' => $can_show_all, 'all_selected' => isset($all_requested)]);
388
		}
389
		else
390 2
		{
391 2
			$base_url = getUrl('topic', ['topic' => $topic, 'subject' => $this->topicinfo['subject']]);
392
			$base_url = str_replace('%', '%%', $base_url);
393
			$context['page_index'] = constructPageIndex($base_url . '.%1$d', $this->_start, $total_visible_posts, $this->messages_per_page, true, ['all' => $can_show_all, 'all_selected' => isset($all_requested)]);
394 2
		}
395 2
		$context['start'] = $this->_start;
396 2
397 2
		// Figure out all the link to the next/prev
398 2
		$context['links'] += [
399
			'prev' => $this->_start >= $this->messages_per_page ? $scripturl . '?topic=' . $topic . '.' . ($this->_start - $this->messages_per_page) : '',
400 2
			'next' => $this->_start + $this->messages_per_page < $total_visible_posts ? $scripturl . '?topic=' . $topic . '.' . ($this->_start + $this->messages_per_page) : '',
401
		];
402
403 2
		// If they are viewing all the posts, show all the posts, otherwise limit the number.
404 2
		if ($can_show_all && isset($all_requested))
405 2
		{
406 2
			// No limit! (actually, there is a limit, but...)
407
			$this->messages_per_page = -1;
408
409 2
			// Set start back to 0...
410
			$this->_start = 0;
411
		}
412 2
	}
413
414
	/**
415 2
	 * Returns the previous or next topic based on the get/query value
416 2
	 * @return void
417 2
	 */
418 2
	public function getPreviousNextTopic(): void
419 2
	{
420
		global $board_info, $topic, $board, $context;
421
422
		$prev_next = $this->_req->getQuery('prev_next', 'trim');
423
424
		// Find the previous or next topic.  Make a fuss if there are no more.
425
		if ($prev_next === 'prev' || $prev_next === 'next')
426
		{
427
			// No use in calculating the next topic if there's only one.
428
			if ($board_info['num_topics'] > 1)
429 2
			{
430 2
				$topic = $prev_next === 'prev'
431 2
					? previousTopic($topic, $board, $this->user->id, $this->getIncludeUnapproved())
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...
432
					: nextTopic($topic, $board, $this->user->id, $this->getIncludeUnapproved());
433
434
				$context['current_topic'] = $topic;
435 2
			}
436 2
437 2
			// Go to the newest message on this topic.
438 2
			$this->_start = 'new';
439 2
		}
440
	}
441 2
442
	/**
443
	 * Add one for the stats
444 2
	 * @param $topic
445
	 */
446 2
	public function increaseTopicViews($topic): void
447
	{
448 2
		if ($this->user->possibly_robot === false
0 ignored issues
show
Bug Best Practice introduced by
The property possibly_robot does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
449 2
			&& (empty($_SESSION['last_read_topic']) || $_SESSION['last_read_topic'] !== $topic))
450
		{
451 2
			increaseViewCounter($topic);
452
			$_SESSION['last_read_topic'] = $topic;
453 2
		}
454
	}
455 2
456 2
	/**
457
	 * Fetch all the topic information.  Provides addons a hook to add additional tables/selects
458 2
	 *
459
	 * @param int $topic
460 2
	 * @param int $board
461
	 * @throws Exception on invalid topic value
462
	 */
463
	public function loadTopicInfo($topic, $board): void
464 2
	{
465
		$topic_selects = [];
466
		$topic_tables = [];
467 2
		$topic_parameters = [
468
			'topic' => $topic,
469 2
			'member' => $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...
470 2
			'board' => (int) $board,
471
		];
472
473
		// Allow addons to add additional details to the topic query
474 2
		call_integration_hook('integrate_topic_query', [&$topic_selects, &$topic_tables, &$topic_parameters]);
475
476
		// Load the topic details
477 2
		$this->topicinfo = getTopicInfo($topic_parameters, 'all', $topic_selects, $topic_tables);
0 ignored issues
show
Documentation Bug introduced by
It seems like getTopicInfo($topic_para...selects, $topic_tables) can also be of type boolean. However, the property $topicinfo is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
478
479 2
		// Nothing??
480
		if (empty($this->topicinfo))
481
		{
482 2
			throw new Exception('not_a_topic', false);
483
		}
484
	}
485
486
	/**
487
	 * Sometimes topics have been moved, this will direct the user to the right spot
488
	 */
489 2
	public function handleRedirection(): void
490
	{
491
		global $context;
492
493 2
		// Need to send the user to the new location?
494 2
		if (!empty($this->topicinfo['id_redirect_topic']) && !isset($this->_req->query->noredir))
495
		{
496 2
			markTopicsRead([$this->user->id, $this->topicinfo['id_topic'], $this->topicinfo['id_last_msg'], 0], $this->topicinfo['new_from'] !== 0);
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...
497 2
			redirectexit('topic=' . $this->topicinfo['id_redirect_topic'] . '.0;redirfrom=' . $this->topicinfo['id_topic']);
498 2
		}
499
500
		// Or are we here because we were redirected?
501 2
		if (isset($this->_req->query->redirfrom))
502
		{
503
			$redirfrom = $this->_req->getQuery('redirfrom', 'intval');
504
			$redir_topics = topicsList([$redirfrom]);
505
			if (!empty($redir_topics[$redirfrom]))
506
			{
507 2
				$context['topic_redirected_from'] = $redir_topics[$redirfrom];
508
				$context['topic_redirected_from']['redir_href'] = getUrl('topic', ['topic' => $context['topic_redirected_from']['id_topic'], 'start' => '0', 'subject' => $context['topic_redirected_from']['subject'], 'noredir']);
509 2
			}
510 2
		}
511
	}
512
513 2
	/**
514 2
	 * Number of posts that this user can see.  Will included unapproved for those with access
515 2
	 *
516
	 * @param int $num_replies
517 2
	 * @return int
518
	 */
519
	public function getVisiblePosts($num_replies): int
520 2
	{
521
		if (!$this->includeUnapproved && $this->topicinfo['unapproved_posts'] && $this->user->is_guest === false)
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...
522
		{
523
			$myUnapprovedPosts = unapprovedPosts($this->topicinfo['id_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...
524 2
525 2
			return $num_replies + $myUnapprovedPosts + ($this->topicinfo['approved'] ? 1 : 0);
526 2
		}
527
528
		if ($this->user->is_guest)
529
		{
530
			return $num_replies + ($this->topicinfo['approved'] ? 1 : 0);
531
		}
532
533
		return $num_replies + $this->topicinfo['unapproved_posts'] + ($this->topicinfo['approved'] ? 1 : 0);
534
	}
535
536
	/**
537 2
	 * The start value from get can contain all manner of information on what to do.
538
	 * This converts new, from, msg into something useful, most times.
539
	 *
540 2
	 * @param int $total_visible_posts
541
	 */
542
	public function makeStartAdjustments($total_visible_posts): void
543 2
	{
544
		global $modSettings;
545 2
546
		$start = $this->_start;
547
		if (!is_numeric($start))
548
		{
549 2
			// Redirect to the page and post with new messages
550 2
			if ($start === 'new')
551
			{
552
				// Guests automatically go to the last post.
553
				$start = $this->user->is_guest ? $total_visible_posts - 1 : 'msg' . $this->topicinfo['new_from'];
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...
554
			}
555
556
			// Start from a certain time index, not a message.
557
			if (strpos($start, 'from') === 0)
558
			{
559 2
				$timestamp = (int) substr($start, 4);
560 2
				$start = $timestamp === 0 ? 0 : countNewPosts($this->topicinfo['id_topic'], $this->topicinfo, $timestamp);
561 2
			}
562 2
			elseif (strpos($start, 'msg') === 0)
563
			{
564 2
				$this->_virtual_msg = (int) substr($start, 3);
565 2
				if (!$this->topicinfo['unapproved_posts'] && $this->_virtual_msg >= $this->topicinfo['id_last_msg'])
566
				{
567 2
					$start = $total_visible_posts - 1;
568 2
				}
569
				elseif (!$this->topicinfo['unapproved_posts'] && $this->_virtual_msg <= $this->topicinfo['id_first_msg'])
570
				{
571
					$start = 0;
572
				}
573
				else
574 2
				{
575
					$only_approved = $modSettings['postmod_active'] && $this->topicinfo['unapproved_posts'] && !allowedTo('approve_posts');
576
					$start = countMessagesBefore($this->topicinfo['id_topic'], $this->_virtual_msg, false, $only_approved, $this->user->is_guest === false);
577
				}
578
			}
579 2
		}
580 2
581 2
		$this->start_from = $start;
0 ignored issues
show
Documentation Bug introduced by
It seems like $start can also be of type string. However, the property $start_from is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
582 2
		$this->_start = $start;
583
	}
584 2
585
	/**
586 2
	 * Sets all we know about a message into $context for template consumption.
587
	 * Note: After this processes, some amount of additional context is still added, read
588
	 * the code.
589
	 */
590 2
	public function setMessageContext(): void
591
	{
592
		global $context, $modSettings, $txt, $board_info;
593
594
		// Going to allow this to be indexed by Mr. Robot?
595
		$context['robot_no_index'] = $this->setRobotNoIndex();
596
597
		// Some basics for the template
598
		$context['num_replies'] = $this->topicinfo['num_replies'];
599
		$context['topic_first_message'] = $this->topicinfo['id_first_msg'];
600
		$context['topic_last_message'] = $this->topicinfo['id_last_msg'];
601
		$context['topic_unwatched'] = $this->topicinfo['unwatched'] ?? 0;
602
		$context['start_from'] = $this->start_from;
603
		$context['topic_start_time'] = htmlTime($this->topicinfo['poster_time']);
604
		$context['topic_starter_name'] = $this->topicinfo['poster_name'];
605 2
606
		// Did this user start the topic or not?
607 2
		$context['user']['started'] = $this->didThisUserStart();
608
		$context['topic_starter_id'] = $this->topicinfo['id_member_started'];
609
610
		// Add up unapproved replies to get real number of replies...
611
		$context['real_num_replies'] = $this->topicinfo['num_replies'];
612 2
		if ($modSettings['postmod_active'] && allowedTo('approve_posts'))
613
		{
614
			$context['real_num_replies'] += $this->topicinfo['unapproved_posts'] - ($this->topicinfo['approved'] ? 0 : 1);
615
		}
616
617
		// When was the last time this topic was replied to?  Should we warn them about it?
618 2
		$context['oldTopicError'] = $this->warnOldTopic();
619
620 2
		// Are we showing signatures - or disabled fields?
621
		$context['signature_enabled'] = strpos($modSettings['signature_settings'], '1') === 0;
622
		$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : [];
623
624 2
		// Page title & description
625 2
		$context['page_title'] = $this->topicinfo['subject'];
626 2
		$context['page_description'] = Util::shorten_text(strip_tags($this->topicinfo['body']), 128);
627 2
628
		// Create a previous next string if the selected theme has it as a selected option.
629
		if ($modSettings['enablePreviousNext'])
630 2
		{
631
			$context['links'] += [
632
				'go_prev' => getUrl('topic', ['topic' => $this->topicinfo['id_topic'], 'start' => '0', 'subject' => $this->topicinfo['subject'], 'prev_next' => 'prev']) . '#new',
633 2
				'go_next' => getUrl('topic', ['topic' => $this->topicinfo['id_topic'], 'start' => '0', 'subject' => $this->topicinfo['subject'], 'prev_next' => 'next']) . '#new'
634
			];
635
		}
636
637
		// Build the jump to box
638 2
		$context['jump_to'] = [
639 2
			'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
640 2
			'board_name' => htmlspecialchars(strtr(strip_tags($board_info['name']), ['&amp;' => '&']), ENT_COMPAT),
641 2
			'child_level' => $board_info['child_level'],
642 2
		];
643 2
644
		// Build a list of this board's moderators.
645
		$context['moderators'] = &$board_info['moderators'];
646 2
		$context['link_moderators'] = [];
647
648
		// Information about the current topic...
649 2
		$context['is_locked'] = $this->topicinfo['locked'];
650 2
		$context['is_sticky'] = $this->topicinfo['is_sticky'];
651
		$context['is_very_hot'] = $this->topicinfo['num_replies'] >= $modSettings['hotTopicVeryPosts'];
652
		$context['is_hot'] = $this->topicinfo['num_replies'] >= $modSettings['hotTopicPosts'];
653 2
		$context['is_approved'] = $this->topicinfo['approved'];
654
655
		// Set the class of the current topic,  Hot, not so hot, locked, sticky
656 2
		determineTopicClass($context);
657
658
		// Set the topic's information for the template.
659
		$context['subject'] = $this->topicinfo['subject'];
660
		$context['num_views'] = $this->topicinfo['num_views'];
661
		$context['num_views_text'] = (int) $this->topicinfo['num_views'] === 1 ? $txt['read_one_time'] : sprintf($txt['read_many_times'], $this->topicinfo['num_views']);
662
		$context['mark_unread_time'] = empty($this->_virtual_msg) ? $this->topicinfo['new_from'] : $this->_virtual_msg;
663
664
		// Set a canonical URL for this page.
665
		$context['canonical_url'] = getUrl('topic', ['topic' => $this->topicinfo['id_topic'], 'start' => $context['start'], 'subject' => $this->topicinfo['subject']]);
666 2
667 2
		// For quick reply we need a response prefix in the default forum language.
668
		$context['response_prefix'] = response_prefix();
669 2
670
		$context['messages_per_page'] = $this->messages_per_page;
671
672 2
		// Build the link tree.
673 2
		$context['breadcrumbs'][] = [
674
			'url' => getUrl('topic', ['topic' => $this->topicinfo['id_topic'], 'start' => '0', 'subject' => $this->topicinfo['subject']]),
675 2
			'name' => $this->topicinfo['subject'],
676
		];
677
	}
678
679 2
	/**
680
	 * Sets if this is a page that we do or do not want bots to index
681
	 *
682 2
	 * @return bool
683
	 */
684 2
	public function setRobotNoIndex(): bool
685 2
	{
686 2
		// Let's do some work on what to search index.
687 2
		if (count((array) $this->_req->query) > 2)
688
		{
689
			foreach (['topic', 'board', 'start', session_name()] as $key)
690
			{
691
				if (!isset($this->_req->query->$key))
692
				{
693
					return true;
694
				}
695
			}
696 2
		}
697
698 2
		return !empty($this->_start)
699
			&& (!is_numeric($this->_start) || $this->_start % $this->messages_per_page !== 0);
700 2
	}
701 2
702 2
	/**
703 2
	 * Return if the current user started this topic, as that may provide them additional permissions.
704 2
	 *
705 2
	 * @return bool
706
	 */
707
	public function didThisUserStart(): bool
708
	{
709 2
		return ((int) $this->user->id === (int) $this->topicinfo['id_member_started']) && !$this->user->is_guest;
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...
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...
710 1
	}
711 2
712 2
	/**
713 2
	 * They Hey bub, what's with the necro-bump message
714
	 *
715 2
	 * @return bool
716
	 */
717
	public function warnOldTopic(): bool
718
	{
719 2
		global $modSettings, $board_info;
720 2
721 2
		if (!empty($modSettings['oldTopicDays']) && !empty($board_info['old_posts']))
722
		{
723 2
			$mgsOptions = basicMessageInfo($this->topicinfo['id_last_msg'], true);
724 2
725
			// May return nothing if not approved
726
			if ($mgsOptions !== false)
727 2
			{
728 2
				return $mgsOptions['poster_time'] + $modSettings['oldTopicDays'] * 86400 < time()
729 2
					&& empty($this->topicinfo['is_sticky']);
730
			}
731 2
		}
732
733
		return false;
734 2
	}
735 2
736 2
	/**
737
	 * Marks notifications read for specified messages
738 2
	 *
739 2
	 * @param array $messages An array of message ids
740
	 * @return void
741
	 */
742 2
	private function markNotificationsRead($messages): void
743 2
	{
744 2
		global $modSettings;
745
746 2
		$mark_at_msg = max($messages);
747 2
		if ($mark_at_msg >= $this->topicinfo['id_last_msg'])
748
		{
749
			$mark_at_msg = $modSettings['maxMsgID'];
750 2
		}
751 2
752 2
		// If there are new messages "in view", lets mark any notification for them as read
753
		if ($mark_at_msg >= $this->topicinfo['new_from'])
754 2
		{
755 2
			require_once(SUBSDIR . '/Mentions.subs.php');
756 2
			$filterMessages = array_filter($messages, static function ($element) use ($mark_at_msg) {
757
				return $element >= $mark_at_msg;
758
			});
759
760
			markNotificationsRead($filterMessages);
761 2
		}
762 1
	}
763 2
764 2
	/**
765 2
	 * Keeps track of where the user is in reading this topic.
766
	 *
767 2
	 * @param array $messages
768
	 * @param int $board
769
	 */
770 2
	private function markRead($messages, $board): void
771 2
	{
772 2
		global $modSettings;
773
774 2
		// Guests can't mark topics read or for notifications, just can't, sorry.
775 2
		if ($this->user->is_guest || empty($messages))
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...
776
		{
777
			return;
778 2
		}
779 2
780 2
		$boardseen = isset($this->_req->query->boardseen);
781
782 2
		$mark_at_msg = max($messages);
783
		if ($mark_at_msg >= $this->topicinfo['id_last_msg'])
784
		{
785 2
			$mark_at_msg = $modSettings['maxMsgID'];
786 2
		}
787 2
788
		if ($mark_at_msg >= $this->topicinfo['new_from'])
789 2
		{
790
			markTopicsRead([$this->user->id, $this->topicinfo['id_topic'], $mark_at_msg, $this->topicinfo['unwatched']], $this->topicinfo['new_from'] !== 0);
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...
791
			$numNewTopics = getUnreadCountSince($board, empty($_SESSION['id_msg_last_visit']) ? 0 : $_SESSION['id_msg_last_visit']);
792 2
793 2
			if (empty($numNewTopics))
794 2
			{
795
				$boardseen = true;
796 2
			}
797
		}
798
799
		updateReadNotificationsFor($this->topicinfo['id_topic'], $board);
800
801 2
		// Mark board as seen if we came using last post link from BoardIndex. (or other places...)
802
		if ($boardseen)
803
		{
804
			require_once(SUBSDIR . '/Boards.subs.php');
805
			markBoardsRead($board, false, false);
806
		}
807
	}
808
809
	/**
810
	 * If the QR is on, we need to load the user information into $context, so we
811
	 * can show the new improved 2.0 QR area
812 2
	 */
813
	public function prepareQuickReply(): void
814 2
	{
815
		global $options, $context;
816
817 2
		if (empty($options['hide_poster_area']) && $options['display_quick_reply'])
818
		{
819
			// First lets load the profile array
820 2
			$thisUser = MembersList::get(User::$info->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...
821 2
			$thisUser->loadContext();
822 2
			$context['thisMember'] = [
823
				'id' => 'new',
824
				'is_message_author' => true,
825
				'member' => $thisUser->toArray()['data']
826
			];
827
		}
828
	}
829
830
	/**
831
	 * Sets if we are showing signatures or not
832
	 */
833
	public function setSignatureShowStatus(): void
834
	{
835
		global $modSettings;
836
837
		[$sig_limits] = explode(':', $modSettings['signature_settings']);
838
		$signature_settings = explode(',', $sig_limits);
839
		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...
840
		{
841
			$this->_show_signatures = empty($signature_settings[8]) ? 0 : (int) $signature_settings[8];
842
		}
843
		else
844
		{
845
			$this->_show_signatures = empty($signature_settings[9]) ? 0 : (int) $signature_settings[9];
846
		}
847
	}
848
849
	/**
850
	 * Loads into context the various message/topic permissions so the template
851
	 * knows what buttons etc. to show
852
	 */
853
	public function setTopicCanPermissions(): void
854
	{
855
		global $modSettings, $context, $settings, $board;
856
857
		// First the common ones
858
		$common_permissions = [
859
			'can_approve' => 'approve_posts',
860
			'can_ban' => 'manage_bans',
861
			'can_sticky' => 'make_sticky',
862
			'can_merge' => 'merge_any',
863
			'can_split' => 'split_any',
864
			'can_mark_notify' => 'mark_any_notify',
865
			'can_send_pm' => 'pm_send',
866
			'can_send_email' => 'send_email_to_members',
867
			'can_report_moderator' => 'report_any',
868
			'can_moderate_forum' => 'moderate_forum',
869
			'can_issue_warning' => 'issue_warning',
870
			'can_restore_topic' => 'move_any',
871
			'can_restore_msg' => 'move_any',
872
		];
873
		foreach ($common_permissions as $contextual => $perm)
874
		{
875
			$context[$contextual] = allowedTo($perm);
876
		}
877
878
		// Permissions with _any/_own versions.  $context[YYY] => ZZZ_any/_own.
879
		$anyown_permissions = [
880
			'can_move' => 'move',
881
			'can_lock' => 'lock',
882
			'can_delete' => 'remove',
883
			'can_reply' => 'post_reply',
884
			'can_reply_unapproved' => 'post_unapproved_replies',
885
		];
886
		foreach ($anyown_permissions as $contextual => $perm)
887
		{
888
			$context[$contextual] = allowedTo($perm . '_any') || ($this->didThisUserStart() && allowedTo($perm . '_own'));
889
		}
890
891
		// Cleanup all the permissions with extra stuff...
892
		$context['can_mark_notify'] = $context['can_mark_notify'] && !$context['user']['is_guest'];
893
		$context['can_reply'] = $context['can_reply'] && (empty($this->topicinfo['locked']) || allowedTo('moderate_board'));
894
		$context['can_reply_unapproved'] = $context['can_reply_unapproved'] && $modSettings['postmod_active'] && (empty($this->topicinfo['locked']) || allowedTo('moderate_board'));
895
		$context['can_issue_warning'] = $context['can_issue_warning'] && featureEnabled('w') && !empty($modSettings['warning_enable']);
896
897
		// Handle approval flags...
898
		$context['can_reply_approved'] = $context['can_reply'];
899
900
		// Guests do not have post_unapproved_replies_own permission, so it's always post_unapproved_replies_any
901
		if ($this->user->is_guest && allowedTo('post_unapproved_replies_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...
902
		{
903
			$context['can_reply_approved'] = false;
904
		}
905
906
		$context['can_reply'] = $context['can_reply'] || $context['can_reply_unapproved'];
907
		$context['can_quote'] = $context['can_reply'] && (empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])));
908
		$context['can_mark_unread'] = $this->user->is_guest === false && $settings['show_mark_read'];
909
		$context['can_unwatch'] = $this->user->is_guest === false && $modSettings['enable_unwatch'];
910
		$context['can_print'] = empty($modSettings['disable_print_topic']);
911
912
		// Start this off for quick moderation - it will be or'd for each post.
913
		$context['can_remove_post'] = allowedTo('delete_any') || (allowedTo('delete_replies') && $this->didThisUserStart());
914
915
		// Can restore topic?  That's if the topic is in the recycle board and has a previous restore state.
916
		$context['can_restore_topic'] = $context['can_restore_topic'] && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($this->topicinfo['id_previous_board']);
917
		$context['can_restore_msg'] = $context['can_restore_msg'] && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($this->topicinfo['id_previous_topic']);
918
	}
919
920
	/**
921
	 * Loads into $context the normal button array for template use.
922
	 * Calls integrate_display_buttons hook
923
	 */
924
	public function buildNormalButtons(): void
925
	{
926
		global $context, $txt;
927
928
		// Build the normal button array.
929
		$context['normal_buttons'] = [
930
			'reply' => [
931
				'test' => 'can_reply',
932
				'text' => 'reply',
933
				'lang' => true,
934
				'url' => getUrl('action', ['action' => 'post', 'topic' => $context['current_topic'] . '.' . $context['start'], 'last_msg' => $this->topicinfo['id_last_msg']]),
935
				'active' => true,
936
			],
937
			'notify' => [
938
				'test' => 'can_mark_notify',
939
				'text' => $context['is_marked_notify'] ? 'unnotify' : 'notify',
940
				'lang' => true,
941
				'custom' => 'onclick="return notifyButton(this);"',
942
				'url' => getUrl('action', ['action' => 'notify', 'sa' => $context['is_marked_notify'] ? 'off' : 'on', 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}']),
943
			],
944
			'mark_unread' => [
945
				'test' => 'can_mark_unread',
946
				'text' => 'mark_unread',
947
				'lang' => true,
948
				'url' => getUrl('action', ['action' => 'markasread', 'sa' => 'topic', 't' => $context['mark_unread_time'], 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}']),
949
			],
950
			'unwatch' => [
951
				'test' => 'can_unwatch',
952
				'text' => ($context['topic_unwatched'] ? '' : 'un') . 'watch',
953
				'lang' => true,
954
				'custom' => 'onclick="return unwatchButton(this);"',
955
				'url' => getUrl('action', ['action' => 'unwatchtopic', 'sa' => $context['topic_unwatched'] ? 'off' : 'on', 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}']),
956
				'submenu' => true,
957
			],
958
			'print' => [
959
				'test' => 'can_print',
960
				'text' => 'print',
961
				'lang' => true,
962
				'custom' => 'rel="nofollow"',
963
				'class' => 'new_win',
964
				'url' => getUrl('action', ['action' => 'topic', 'sa' => 'printpage', 'topic' => $context['current_topic'] . '.0']),
965
				'submenu' => true,
966
			],
967
		];
968
969
		// Allow adding new buttons easily.
970
		call_integration_hook('integrate_display_buttons');
971
	}
972
973
	/**
974
	 * Loads into $context the moderation button array for template use.
975
	 * Call integrate_mod_buttons hook
976
	 */
977
	public function buildModerationButtons(): void
978
	{
979
		global $context, $txt;
980
981
		// Build the mod button array
982
		$context['mod_buttons'] = [
983
			'move' => [
984
				'test' => 'can_move',
985
				'text' => 'move_topic',
986
				'lang' => true,
987
				'url' => getUrl('action', ['action' => 'movetopic', 'current_board' => $context['current_board'], 'topic' => $context['current_topic'] . '.0'])
988
			],
989
			'delete' => [
990
				'test' => 'can_delete',
991
				'text' => 'remove_topic',
992
				'lang' => true,
993
				'custom' => 'onclick="return confirm(\'' . $txt['are_sure_remove_topic'] . '\');"',
994
				'url' => getUrl('action', ['action' => 'removetopic2', 'topic' => $context['current_topic'] . '.0', '{session_data}'])
995
			],
996
			'lock' => [
997
				'test' => 'can_lock',
998
				'text' => empty($this->topicinfo['locked']) ? 'set_lock' : 'set_unlock',
999
				'lang' => true,
1000
				'url' => getUrl('action', ['action' => 'topic', 'sa' => 'lock', 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}'])
1001
			],
1002
			'sticky' => [
1003
				'test' => 'can_sticky',
1004
				'text' => empty($this->topicinfo['is_sticky']) ? 'set_sticky' : 'set_nonsticky',
1005
				'lang' => true,
1006
				'url' => getUrl('action', ['action' => 'topic', 'sa' => 'sticky', 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}'])
1007
			],
1008
			'merge' => [
1009
				'test' => 'can_merge',
1010
				'text' => 'merge',
1011
				'lang' => true,
1012
				'url' => getUrl('action', ['action' => 'mergetopics', 'board' => $context['current_board'] . '.0', 'from' => $context['current_topic']])
1013
			],
1014
		];
1015
1016
		// Restore topic. eh?  No monkey business.
1017
		if ($context['can_restore_topic'])
1018
		{
1019
			$context['mod_buttons'][] = [
1020
				'text' => 'restore_topic',
1021
				'lang' => true,
1022
				'url' => getUrl('action', ['action' => 'restoretopic', 'topics' => $context['current_topic'], '{session_data}'])
1023
			];
1024
		}
1025
1026
		// Allow adding new buttons easily.
1027
		call_integration_hook('integrate_mod_buttons');
1028
	}
1029
1030
	/**
1031
	 * If we are in a topic and don't have permission to approve it then duck out now.
1032
	 * This is an abuse of the method, but it's easier that way.
1033
	 *
1034
	 * @param string $action the function name of the current action
1035
	 *
1036
	 * @return bool
1037
	 * @throws Exception not_a_topic
1038
	 */
1039
	public function trackStats($action = '')
1040
	{
1041
		global $topic, $board_info;
1042
1043
		if (!empty($topic)
1044
			&& empty($board_info['cur_topic_approved'])
1045
			&& ($this->user->id != $board_info['cur_topic_starter'] || $this->user->is_guest)
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...
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...
1046
			&& !allowedTo('approve_posts'))
1047
		{
1048
			throw new Exception('not_a_topic', false);
1049
		}
1050
1051
		return parent::trackStats($action);
1052
	}
1053
1054
	/**
1055
	 * In-topic quick moderation.
1056
	 *
1057
	 * Accessed by ?action=quickmod2 from quickModForm
1058
	 */
1059
	public function action_quickmod2(): void
1060
	{
1061
		global $topic, $board, $context, $modSettings;
1062
1063
		// Check the session = get or post.
1064
		checkSession('request');
1065
1066
		require_once(SUBSDIR . '/Messages.subs.php');
1067
1068
		if (empty($this->_req->post->msgs))
1069
		{
1070
			redirectexit('topic=' . $topic . '.' . $this->_req->getQuery('start', 'intval'));
1071
		}
1072
1073
		$messages = array_map('intval', $this->_req->post->msgs);
1074
1075
		// We are restoring messages. We handle this in another place.
1076
		if (isset($this->_req->query->restore_selected))
1077
		{
1078
			redirectexit('action=restoretopic;msgs=' . implode(',', $messages) . ';' . $context['session_var'] . '=' . $context['session_id']);
1079
		}
1080
1081
		if (isset($this->_req->query->split_selection))
1082
		{
1083
			$mgsOptions = basicMessageInfo(min($messages), true);
1084
1085
			$_SESSION['split_selection'][$topic] = $messages;
1086
			redirectexit('action=splittopics;sa=selectTopics;topic=' . $topic . '.0;subname_enc=' . urlencode($mgsOptions['subject']) . ';' . $context['session_var'] . '=' . $context['session_id']);
1087
		}
1088
1089
		require_once(SUBSDIR . '/Topic.subs.php');
1090
		$topic_info = getTopicInfo($topic);
1091
1092
		// Allowed to delete any message?
1093
		$allowed_all = $this->canDeleteAll($topic_info);
1094
1095
		// Make sure they're allowed to delete their own messages, if not any.
1096
		if (!$allowed_all)
1097
		{
1098
			isAllowedTo('delete_own');
1099
		}
1100
1101
		// Allowed to remove which messages?
1102
		$messages = determineRemovableMessages($topic, $messages, $allowed_all);
1103
1104
		// Get the first message in the topic - because you can't delete that!
1105
		$first_message = (int) $topic_info['id_first_msg'];
1106
		$last_message = (int) $topic_info['id_last_msg'];
1107
		$remover = new MessagesDelete($modSettings['recycle_enable'], $modSettings['recycle_board']);
1108
1109
		// Delete all the messages we know they can delete. ($messages)
1110
		foreach ($messages as $message => $info)
1111
		{
1112
			$message = (int) $message;
1113
1114
			// Just skip the first message - if it's not the last.
1115
			if ($message === $first_message && $message !== $last_message)
1116
			{
1117
				continue;
1118
			}
1119
1120
			// If the first message is going then don't bother going back to the topic as we're effectively deleting it.
1121
			if ($message === $first_message)
1122
			{
1123
				$topicGone = true;
1124
			}
1125
1126
			$remover->removeMessage($message);
1127
		}
1128
1129
		redirectexit(empty($topicGone) ? 'topic=' . $topic . '.' . (int) $this->_req->query->start : 'board=' . $board);
1130
	}
1131
1132
	/**
1133
	 * Determine if this user can delete all replies in this message
1134
	 *
1135
	 * @param array $topic_info
1136
	 * @return bool
1137
	 */
1138
	public function canDeleteAll($topic_info): bool
1139
	{
1140
		if (allowedTo('delete_any'))
1141
		{
1142
			return true;
1143
		}
1144
1145
		// Allowed to delete replies to their messages?
1146
		if (allowedTo('delete_replies'))
1147
		{
1148
			return (int) $topic_info['id_member_started'] === (int) $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...
1149
		}
1150
1151
		return false;
1152
	}
1153
}
1154