Passed
Pull Request — development (#3796)
by Spuds
08:24
created

Display::handleRedirection()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 5.1158

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 6
nop 0
dl 0
loc 20
ccs 5
cts 6
cp 0.8333
crap 5.1158
rs 9.6111
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()
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', array('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', array(&$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 do XML preview here.
251
			'preview_type' => 1,
252
		];
253
254
		// Load the template basics now as template_layers is requested by the prepare_context event
255
		theme()->getTemplates()->load('Display');
256
		$this->_template_layers = theme()->getLayers();
257
		$this->_template_layers->addEnd('messages_informations');
258 2
		$context['sub_template'] = 'messages';
259
260
		// Trigger the prepare_context event for modules that have tied in to it
261
		$this->_events->trigger('prepare_context', ['editorOptions' => &$editorOptions, 'use_quick_reply' => !empty($options['display_quick_reply'])]);
262
263 2
		// Load up the "double post" sequencing magic.
264
		if (!empty($options['display_quick_reply']))
265
		{
266
			checkSubmitOnce('register');
267
			$context['name'] = $_SESSION['guest_name'] ?? '';
268
			$context['email'] = $_SESSION['guest_email'] ?? '';
269
			if ($context['can_reply'])
270
			{
271
				// Needed for the editor and message icons.
272
				require_once(SUBSDIR . '/Editor.subs.php');
273
				create_control_richedit($editorOptions);
274
			}
275
		}
276
277
		theme()->addJavascriptVar(['notification_topic_notice' => $context['is_marked_notify'] ? $txt['notification_disable_topic'] : $txt['notification_enable_topic']], true);
278 2
279
		// Build the common to all buttons like Reply Notify Mark ....
280 2
		$this->buildNormalButtons();
281 2
282
		// Build specialized buttons, like moderation
283
		$this->buildModerationButtons();
284
285 2
		// Let's get nosey, who is viewing this topic?
286
		if (!empty($settings['display_who_viewing']))
287 2
		{
288
			require_once(SUBSDIR . '/Who.subs.php');
289
			formatViewers($this->topicinfo['id_topic'], 'topic');
290
		}
291
292
		// Did we report a post to a moderator just now?
293
		if (isset($this->_req->query->reportsent))
294
		{
295
			$this->_template_layers->add('report_sent');
296 2
		}
297
298
		// All of our buttons and bottom navigation
299
		$this->_template_layers->add('moderation_buttons');
300
		$this->_template_layers->add('pages_and_buttons');
301 2
302
		// Quick reply & modify enabled?
303
		if ($context['can_reply'] && !empty($options['display_quick_reply']))
304 2
		{
305 2
			loadJavascriptFile(['editor/mentioning.js', 'quickQuote.js'], ['defer' => true]);
306
			$this->_template_layers->addBefore('quickreply', 'moderation_buttons');
307
			theme()->addInlineJavascript("
308
				document.addEventListener('DOMContentLoaded', () => new Elk_QuickQuote(), false);", true
309
			);
310 2
		}
311 2
	}
312
313
	/**
314
	 * Sets the message per page
315
	 */
316
	public function setMessagesPerPage()
317 2
	{
318 2
		global $modSettings, $options;
319
320
		$this->messages_per_page = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? (int) $options['messages_per_page'] : (int) $modSettings['defaultMaxMessages'];
321 2
	}
322 2
323
	/**
324
	 * Returns IF we are counting unapproved posts
325 2
	 *
326
	 * @return bool
327
	 */
328 2
	public function getIncludeUnapproved()
329
	{
330
		global $modSettings;
331 2
332 2
		return !$modSettings['postmod_active'] || allowedTo('approve_posts');
333
	}
334
335
	/**
336
	 * Return if we allow showing ALL messages for a topic vs pagination
337
	 *
338 2
	 * @param int $total_visible_posts
339
	 * @return bool
340 2
	 */
341 2
	public function getCanShowAll($total_visible_posts)
342
	{
343
		global $modSettings;
344
345 2
		return !empty($modSettings['enableAllMessages'])
346 2
			&& $total_visible_posts > $this->messages_per_page
347
			&& $total_visible_posts < $modSettings['enableAllMessages'];
348
	}
349
350
	/**
351 2
	 * If show all is requested, and allowed, setup to do just that
352
	 *
353
	 * @param bool $can_show_all
354
	 * @param int $total_visible_posts
355
	 * @return void
356
	 */
357 2
	public function setupShowAll($can_show_all, $total_visible_posts)
358 2
	{
359
		global $scripturl, $topic, $context;
360
361
		$all_requested = $this->_req->getQuery('all', 'trim', null);
362 2
		if (isset($all_requested))
363 2
		{
364 2
			// If all is set, but not allowed... just unset it.
365
			if (!$can_show_all)
366
			{
367
				unset($all_requested);
368
			}
369 2
			else
370 2
			{
371
				// Otherwise, it must be allowed... so pretend start was -1.
372
				$this->_start = -1;
373
			}
374 2
		}
375
376
		// Construct the page index, allowing for the .START method...
377
		$context['page_index'] = constructPageIndex($scripturl . '?topic=' . $topic . '.%1$d', $this->_start, $total_visible_posts, $this->messages_per_page, true, array('all' => $can_show_all, 'all_selected' => isset($all_requested)));
378
		$context['start'] = $this->_start;
379
380
		// Figure out all the link to the next/prev
381
		$context['links'] += array(
382
			'prev' => $this->_start >= $this->messages_per_page ? $scripturl . '?topic=' . $topic . '.' . ($this->_start - $this->messages_per_page) : '',
383
			'next' => $this->_start + $this->messages_per_page < $total_visible_posts ? $scripturl . '?topic=' . $topic . '.' . ($this->_start + $this->messages_per_page) : '',
384 2
		);
385 2
386 2
		// If they are viewing all the posts, show all the posts, otherwise limit the number.
387
		if ($can_show_all && isset($all_requested))
388
		{
389
			// No limit! (actually, there is a limit, but...)
390 2
			$this->messages_per_page = -1;
391 2
392
			// Set start back to 0...
393
			$this->_start = 0;
394 2
		}
395 2
	}
396 2
397 2
	/**
398 2
	 * Returns the previous or next topic based on the get/query value
399
	 * @return void
400 2
	 */
401
	public function getPreviousNextTopic()
402
	{
403 2
		global $board_info, $topic, $board, $context;
404 2
405 2
		$prev_next = $this->_req->getQuery('prev_next', 'trim');
406 2
407
		// Find the previous or next topic.  Make a fuss if there are no more.
408
		if ($prev_next === 'prev' || $prev_next === 'next')
409 2
		{
410
			// No use in calculating the next topic if there's only one.
411
			if ($board_info['num_topics'] > 1)
412 2
			{
413
				$topic = $prev_next === 'prev'
414
					? 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...
415 2
					: nextTopic($topic, $board, $this->user->id, $this->getIncludeUnapproved());
416 2
417 2
				$context['current_topic'] = $topic;
418 2
			}
419 2
420
			// Go to the newest message on this topic.
421
			$this->_start = 'new';
422
		}
423
	}
424
425
	/**
426
	 * Add one for the stats
427
	 * @param $topic
428
	 */
429 2
	public function increaseTopicViews($topic)
430 2
	{
431 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...
432
			&& (empty($_SESSION['last_read_topic']) || $_SESSION['last_read_topic'] !== $topic))
433
		{
434
			increaseViewCounter($topic);
435 2
			$_SESSION['last_read_topic'] = $topic;
436 2
		}
437 2
	}
438 2
439 2
	/**
440
	 * Fetch all the topic information.  Provides addons a hook to add additional tables/selects
441 2
	 *
442
	 * @param int $topic
443
	 * @param int $board
444 2
	 * @throws \ElkArte\Exceptions\Exception on invalid topic value
445
	 */
446 2
	public function loadTopicInfo($topic, $board)
447
	{
448 2
		$topic_selects = [];
449 2
		$topic_tables = [];
450
		$topic_parameters = [
451 2
			'topic' => $topic,
452
			'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...
453 2
			'board' => (int) $board,
454
		];
455 2
456 2
		// Allow addons to add additional details to the topic query
457
		call_integration_hook('integrate_topic_query', array(&$topic_selects, &$topic_tables, &$topic_parameters));
458 2
459
		// Load the topic details
460 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...
461
462
		// Nothing??
463
		if (empty($this->topicinfo))
464 2
		{
465
			throw new Exception('not_a_topic', false);
466
		}
467 2
	}
468
469 2
	/**
470 2
	 * Sometimes topics have been moved, this will direct the user to the right spot
471
	 */
472
	public function handleRedirection()
473
	{
474 2
		global $context;
475
476
		// Need to send the user to the new location?
477 2
		if (!empty($this->topicinfo['id_redirect_topic']) && !isset($this->_req->query->noredir))
478
		{
479 2
			markTopicsRead(array($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...
480
			redirectexit('topic=' . $this->topicinfo['id_redirect_topic'] . '.0;redirfrom=' . $this->topicinfo['id_topic']);
481
		}
482 2
483
		// Or are we here because we were redirected?
484
		if (isset($this->_req->query->redirfrom))
485
		{
486
			$redirfrom = $this->_req->getQuery('redirfrom', 'intval');
487
			$redir_topics = topicsList(array($redirfrom));
488
			if (!empty($redir_topics[$redirfrom]))
489 2
			{
490
				$context['topic_redirected_from'] = $redir_topics[$redirfrom];
491
				$context['topic_redirected_from']['redir_href'] = getUrl('topic', ['topic' => $context['topic_redirected_from']['id_topic'], 'start' => '0', 'subject' => $context['topic_redirected_from']['subject'], 'noredir']);
492
			}
493 2
		}
494 2
	}
495
496 2
	/**
497 2
	 * Number of posts that this user can see.  Will included unapproved for those with access
498 2
	 *
499
	 * @param int $num_replies
500
	 * @return int
501 2
	 */
502
	public function getVisiblePosts($num_replies)
503
	{
504
		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...
505
		{
506
			$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...
507 2
508
			return $num_replies + $myUnapprovedPosts + ($this->topicinfo['approved'] ? 1 : 0);
509 2
		}
510 2
511
		if ($this->user->is_guest)
512
		{
513 2
			return $num_replies + ($this->topicinfo['approved'] ? 1 : 0);
514 2
		}
515 2
516
		return $num_replies + $this->topicinfo['unapproved_posts'] + ($this->topicinfo['approved'] ? 1 : 0);
517 2
	}
518
519
	/**
520 2
	 * The start value from get can contain all manner of information on what to do.
521
	 * This converts new, from, msg into something useful, most times.
522
	 *
523
	 * @param int $total_visible_posts
524 2
	 */
525 2
	public function makeStartAdjustments($total_visible_posts)
526 2
	{
527
		global $modSettings;
528
529
		$start = $this->_start;
530
		if (!is_numeric($start))
531
		{
532
			// Redirect to the page and post with new messages
533
			if ($start === 'new')
534
			{
535
				// Guests automatically go to the last post.
536
				$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...
537 2
			}
538
539
			// Start from a certain time index, not a message.
540 2
			if (strpos($start, 'from') === 0)
541
			{
542
				$timestamp = (int) substr($start, 4);
543 2
				$start = $timestamp === 0 ? 0 : countNewPosts($this->topicinfo['id_topic'], $this->topicinfo, $timestamp);
544
			}
545 2
			elseif (strpos($start, 'msg') === 0)
546
			{
547
				$this->_virtual_msg = (int) substr($start, 3);
548
				if (!$this->topicinfo['unapproved_posts'] && $this->_virtual_msg >= $this->topicinfo['id_last_msg'])
549 2
				{
550 2
					$start = $total_visible_posts - 1;
551
				}
552
				elseif (!$this->topicinfo['unapproved_posts'] && $this->_virtual_msg <= $this->topicinfo['id_first_msg'])
553
				{
554
					$start = 0;
555
				}
556
				else
557
				{
558
					$only_approved = $modSettings['postmod_active'] && $this->topicinfo['unapproved_posts'] && !allowedTo('approve_posts');
559 2
					$start = countMessagesBefore($this->topicinfo['id_topic'], $this->_virtual_msg, false, $only_approved, $this->user->is_guest === false);
560 2
				}
561 2
			}
562 2
		}
563
564 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...
565 2
		$this->_start = $start;
566
	}
567 2
568 2
	/**
569
	 * Sets all we know about a message into $context for template consumption.
570
	 * Note: After this processes, some amount of additional context is still added, read
571
	 * the code.
572
	 */
573
	public function setMessageContext()
574 2
	{
575
		global $context, $modSettings, $txt, $board_info;
576
577
		// Going to allow this to be indexed by Mr. Robot?
578
		$context['robot_no_index'] = $this->setRobotNoIndex();
579 2
580 2
		// Some basics for the template
581 2
		$context['num_replies'] = $this->topicinfo['num_replies'];
582 2
		$context['topic_first_message'] = $this->topicinfo['id_first_msg'];
583
		$context['topic_last_message'] = $this->topicinfo['id_last_msg'];
584 2
		$context['topic_unwatched'] = $this->topicinfo['unwatched'] ?? 0;
585
		$context['start_from'] = $this->start_from;
586 2
587
		// Did this user start the topic or not?
588
		$context['user']['started'] = $this->didThisUserStart();
589
		$context['topic_starter_id'] = $this->topicinfo['id_member_started'];
590 2
591
		// Add up unapproved replies to get real number of replies...
592
		$context['real_num_replies'] = $this->topicinfo['num_replies'];
593
		if ($modSettings['postmod_active'] && allowedTo('approve_posts'))
594
		{
595
			$context['real_num_replies'] += $this->topicinfo['unapproved_posts'] - ($this->topicinfo['approved'] ? 0 : 1);
596
		}
597
598
		// When was the last time this topic was replied to?  Should we warn them about it?
599
		$context['oldTopicError'] = $this->warnOldTopic();
600
601
		// Are we showing signatures - or disabled fields?
602
		$context['signature_enabled'] = strpos($modSettings['signature_settings'], '1') === 0;
603
		$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : [];
604
605 2
		// Page title & description
606
		$context['page_title'] = $this->topicinfo['subject'];
607 2
		$context['page_description'] = Util::shorten_text(strip_tags($this->topicinfo['body']), 128);
608
609
		// Create a previous next string if the selected theme has it as a selected option.
610
		if ($modSettings['enablePreviousNext'])
611
		{
612 2
			$context['links'] += [
613
				'go_prev' => getUrl('topic', ['topic' => $this->topicinfo['id_topic'], 'start' => '0', 'subject' => $this->topicinfo['subject'], 'prev_next' => 'prev']) . '#new',
614
				'go_next' => getUrl('topic', ['topic' => $this->topicinfo['id_topic'], 'start' => '0', 'subject' => $this->topicinfo['subject'], 'prev_next' => 'next']) . '#new'
615
			];
616
		}
617
618 2
		// Build the jump to box
619
		$context['jump_to'] = [
620 2
			'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
621
			'board_name' => htmlspecialchars(strtr(strip_tags($board_info['name']), ['&amp;' => '&']), ENT_COMPAT),
622
			'child_level' => $board_info['child_level'],
623
		];
624 2
625 2
		// Build a list of this board's moderators.
626 2
		$context['moderators'] = &$board_info['moderators'];
627 2
		$context['link_moderators'] = [];
628
629
		// Information about the current topic...
630 2
		$context['is_locked'] = $this->topicinfo['locked'];
631
		$context['is_sticky'] = $this->topicinfo['is_sticky'];
632
		$context['is_very_hot'] = $this->topicinfo['num_replies'] >= $modSettings['hotTopicVeryPosts'];
633 2
		$context['is_hot'] = $this->topicinfo['num_replies'] >= $modSettings['hotTopicPosts'];
634
		$context['is_approved'] = $this->topicinfo['approved'];
635
636
		// Set the class of the current topic,  Hot, not so hot, locked, sticky
637
		determineTopicClass($context);
638 2
639 2
		// Set the topic's information for the template.
640 2
		$context['subject'] = $this->topicinfo['subject'];
641 2
		$context['num_views'] = $this->topicinfo['num_views'];
642 2
		$context['num_views_text'] = (int) $this->topicinfo['num_views'] === 1 ? $txt['read_one_time'] : sprintf($txt['read_many_times'], $this->topicinfo['num_views']);
643 2
		$context['mark_unread_time'] = empty($this->_virtual_msg) ? $this->topicinfo['new_from'] : $this->_virtual_msg;
644
645
		// Set a canonical URL for this page.
646 2
		$context['canonical_url'] = getUrl('topic', ['topic' => $this->topicinfo['id_topic'], 'start' => $context['start'], 'subject' => $this->topicinfo['subject']]);
647
648
		// For quick reply we need a response prefix in the default forum language.
649 2
		$context['response_prefix'] = response_prefix();
650 2
651
		$context['messages_per_page'] = $this->messages_per_page;
652
653 2
		// Build the link tree.
654
		$context['breadcrumbs'][] = [
655
			'url' => getUrl('topic', ['topic' => $this->topicinfo['id_topic'], 'start' => '0', 'subject' => $this->topicinfo['subject']]),
656 2
			'name' => $this->topicinfo['subject'],
657
		];
658
	}
659
660
	/**
661
	 * Sets if this is a page that we do or do not want bots to index
662
	 *
663
	 * @return bool
664
	 */
665
	public function setRobotNoIndex()
666 2
	{
667 2
		// Let's do some work on what to search index.
668
		if (count((array) $this->_req->query) > 2)
669 2
		{
670
			foreach (['topic', 'board', 'start', session_name()] as $key)
671
			{
672 2
				if (!isset($this->_req->query->$key))
673 2
				{
674
					return true;
675 2
				}
676
			}
677
		}
678
679 2
		return !empty($this->_start)
680
			&& (!is_numeric($this->_start) || $this->_start % $this->messages_per_page !== 0);
681
	}
682 2
683
	/**
684 2
	 * Return if the current user started this topic, as that may provide them additional permissions.
685 2
	 *
686 2
	 * @return bool
687 2
	 */
688
	public function didThisUserStart()
689
	{
690
		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...
691
	}
692
693
	/**
694
	 * They Hey bub, what's with the necro-bump message
695
	 *
696 2
	 * @return bool
697
	 */
698 2
	public function warnOldTopic()
699
	{
700 2
		global $modSettings, $board_info;
701 2
702 2
		if (!empty($modSettings['oldTopicDays']) && !empty($board_info['old_posts']))
703 2
		{
704 2
			$mgsOptions = basicMessageInfo($this->topicinfo['id_last_msg'], true);
705 2
706
			// May return nothing if not approved
707
			if ($mgsOptions !== false)
708
			{
709 2
				return $mgsOptions['poster_time'] + $modSettings['oldTopicDays'] * 86400 < time()
710 1
					&& empty($this->topicinfo['is_sticky']);
711 2
			}
712 2
		}
713 2
714
		return false;
715 2
	}
716
717
	/**
718
	 * Marks notifications read for specified messages
719 2
	 *
720 2
	 * @param array $messages An array of message ids
721 2
	 * @return void
722
	 */
723 2
	private function markNotificationsRead($messages)
724 2
	{
725
		global $modSettings;
726
727 2
		$mark_at_msg = max($messages);
728 2
		if ($mark_at_msg >= $this->topicinfo['id_last_msg'])
729 2
		{
730
			$mark_at_msg = $modSettings['maxMsgID'];
731 2
		}
732
733
		// If there are new messages "in view", lets mark any notification for them as read
734 2
		if ($mark_at_msg >= $this->topicinfo['new_from'])
735 2
		{
736 2
			require_once(SUBSDIR . '/Mentions.subs.php');
737
			$filterMessages = array_filter($messages, static function ($element) use ($mark_at_msg) {
738 2
				return $element >= $mark_at_msg;
739 2
			});
740
741
			markNotificationsRead($filterMessages);
742 2
		}
743 2
	}
744 2
745
	/**
746 2
	 * Keeps track of where the user is in reading this topic.
747 2
	 *
748
	 * @param array $messages
749
	 * @param int $board
750 2
	 */
751 2
	private function markRead($messages, $board)
752 2
	{
753
		global $modSettings;
754 2
755 2
		// Guests can't mark topics read or for notifications, just can't, sorry.
756 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...
757
		{
758
			return;
759
		}
760
761 2
		$boardseen = isset($this->_req->query->boardseen);
762 1
763 2
		$mark_at_msg = max($messages);
764 2
		if ($mark_at_msg >= $this->topicinfo['id_last_msg'])
765 2
		{
766
			$mark_at_msg = $modSettings['maxMsgID'];
767 2
		}
768
769
		if ($mark_at_msg >= $this->topicinfo['new_from'])
770 2
		{
771 2
			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...
772 2
			$numNewTopics = getUnreadCountSince($board, empty($_SESSION['id_msg_last_visit']) ? 0 : $_SESSION['id_msg_last_visit']);
773
774 2
			if (empty($numNewTopics))
775 2
			{
776
				$boardseen = true;
777
			}
778 2
		}
779 2
780 2
		updateReadNotificationsFor($this->topicinfo['id_topic'], $board);
781
782 2
		// Mark board as seen if we came using last post link from BoardIndex. (or other places...)
783
		if ($boardseen)
784
		{
785 2
			require_once(SUBSDIR . '/Boards.subs.php');
786 2
			markBoardsRead($board, false, false);
787 2
		}
788
	}
789 2
790
	/**
791
	 * If the QR is on, we need to load the user information into $context, so we
792 2
	 * can show the new improved 2.0 QR area
793 2
	 */
794 2
	public function prepareQuickReply()
795
	{
796 2
		global $options, $context;
797
798
		if (empty($options['hide_poster_area']) && $options['display_quick_reply'])
799
		{
800
			// First lets load the profile array
801 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...
802
			$thisUser->loadContext();
803
			$context['thisMember'] = [
804
				'id' => 'new',
805
				'is_message_author' => true,
806
				'member' => $thisUser->toArray()['data']
807
			];
808
		}
809
	}
810
811
	/**
812 2
	 * Sets if we are showing signatures or not
813
	 */
814 2
	public function setSignatureShowStatus()
815
	{
816
		global $modSettings;
817 2
818
		[$sig_limits] = explode(':', $modSettings['signature_settings']);
819
		$signature_settings = explode(',', $sig_limits);
820 2
		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...
821 2
		{
822 2
			$this->_show_signatures = empty($signature_settings[8]) ? 0 : (int) $signature_settings[8];
823
		}
824
		else
825
		{
826
			$this->_show_signatures = empty($signature_settings[9]) ? 0 : (int) $signature_settings[9];
827
		}
828
	}
829
830
	/**
831
	 * Loads into context the various message/topic permissions so the template
832
	 * knows what buttons etc. to show
833
	 */
834
	public function setTopicCanPermissions()
835
	{
836
		global $modSettings, $context, $settings, $board;
837
838
		// First the common ones
839
		$common_permissions = array(
840
			'can_approve' => 'approve_posts',
841
			'can_ban' => 'manage_bans',
842
			'can_sticky' => 'make_sticky',
843
			'can_merge' => 'merge_any',
844
			'can_split' => 'split_any',
845
			'can_mark_notify' => 'mark_any_notify',
846
			'can_send_pm' => 'pm_send',
847
			'can_send_email' => 'send_email_to_members',
848
			'can_report_moderator' => 'report_any',
849
			'can_moderate_forum' => 'moderate_forum',
850
			'can_issue_warning' => 'issue_warning',
851
			'can_restore_topic' => 'move_any',
852
			'can_restore_msg' => 'move_any',
853
		);
854
		foreach ($common_permissions as $contextual => $perm)
855
		{
856
			$context[$contextual] = allowedTo($perm);
857
		}
858
859
		// Permissions with _any/_own versions.  $context[YYY] => ZZZ_any/_own.
860
		$anyown_permissions = array(
861
			'can_move' => 'move',
862
			'can_lock' => 'lock',
863
			'can_delete' => 'remove',
864
			'can_reply' => 'post_reply',
865
			'can_reply_unapproved' => 'post_unapproved_replies',
866
		);
867
		foreach ($anyown_permissions as $contextual => $perm)
868
		{
869
			$context[$contextual] = allowedTo($perm . '_any') || ($this->didThisUserStart() && allowedTo($perm . '_own'));
870
		}
871
872
		// Cleanup all the permissions with extra stuff...
873
		$context['can_mark_notify'] = $context['can_mark_notify'] && !$context['user']['is_guest'];
874
		$context['can_reply'] = $context['can_reply'] && (empty($this->topicinfo['locked']) || allowedTo('moderate_board'));
875
		$context['can_reply_unapproved'] = $context['can_reply_unapproved'] && $modSettings['postmod_active'] && (empty($this->topicinfo['locked']) || allowedTo('moderate_board'));
876
		$context['can_issue_warning'] = $context['can_issue_warning'] && featureEnabled('w') && !empty($modSettings['warning_enable']);
877
878
		// Handle approval flags...
879
		$context['can_reply_approved'] = $context['can_reply'];
880
881
		// Guests do not have post_unapproved_replies_own permission, so it's always post_unapproved_replies_any
882
		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...
883
		{
884
			$context['can_reply_approved'] = false;
885
		}
886
887
		$context['can_reply'] = $context['can_reply'] || $context['can_reply_unapproved'];
888
		$context['can_quote'] = $context['can_reply'] && (empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])));
889
		$context['can_mark_unread'] = $this->user->is_guest === false && $settings['show_mark_read'];
890
		$context['can_unwatch'] = $this->user->is_guest === false && $modSettings['enable_unwatch'];
891
		$context['can_print'] = empty($modSettings['disable_print_topic']);
892
893
		// Start this off for quick moderation - it will be or'd for each post.
894
		$context['can_remove_post'] = allowedTo('delete_any') || (allowedTo('delete_replies') && $this->didThisUserStart());
895
896
		// Can restore topic?  That's if the topic is in the recycle board and has a previous restore state.
897
		$context['can_restore_topic'] = $context['can_restore_topic'] && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($this->topicinfo['id_previous_board']);
898
		$context['can_restore_msg'] = $context['can_restore_msg'] && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($this->topicinfo['id_previous_topic']);
899
	}
900
901
	/**
902
	 * Loads into $context the normal button array for template use.
903
	 * Calls integrate_display_buttons hook
904
	 */
905
	public function buildNormalButtons()
906
	{
907
		global $context, $txt;
908
909
		// Build the normal button array.
910
		$context['normal_buttons'] = [
911
			'reply' => [
912
				'test' => 'can_reply',
913
				'text' => 'reply',
914
				'lang' => true,
915
				'url' => getUrl('action', ['action' => 'post', 'topic' => $context['current_topic'] . '.' . $context['start'], 'last_msg' => $this->topicinfo['id_last_msg']]),
916
				'active' => true,
917
			],
918
			'notify' => [
919
				'test' => 'can_mark_notify',
920
				'text' => $context['is_marked_notify'] ? 'unnotify' : 'notify',
921
				'lang' => true,
922
				'custom' => 'onclick="return notifyButton(this);"',
923
				'url' => getUrl('action', ['action' => 'notify', 'sa' => $context['is_marked_notify'] ? 'off' : 'on', 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}']),
924
			],
925
			'mark_unread' => [
926
				'test' => 'can_mark_unread',
927
				'text' => 'mark_unread',
928
				'lang' => true,
929
				'url' => getUrl('action', ['action' => 'markasread', 'sa' => 'topic', 't' => $context['mark_unread_time'], 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}']),
930
			],
931
			'unwatch' => [
932
				'test' => 'can_unwatch',
933
				'text' => ($context['topic_unwatched'] ? '' : 'un') . 'watch',
934
				'lang' => true,
935
				'custom' => 'onclick="return unwatchButton(this);"',
936
				'url' => getUrl('action', ['action' => 'unwatchtopic', 'sa' => $context['topic_unwatched'] ? 'off' : 'on', 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}']),
937
				'submenu' => true,
938
			],
939
			'print' => [
940
				'test' => 'can_print',
941
				'text' => 'print',
942
				'lang' => true,
943
				'custom' => 'rel="nofollow"',
944
				'class' => 'new_win',
945
				'url' => getUrl('action', ['action' => 'topic', 'sa' => 'printpage', 'topic' => $context['current_topic'] . '.0']),
946
				'submenu' => true,
947
			],
948
		];
949
950
		// Allow adding new buttons easily.
951
		call_integration_hook('integrate_display_buttons');
952
	}
953
954
	/**
955
	 * Loads into $context the moderation button array for template use.
956
	 * Call integrate_mod_buttons hook
957
	 */
958
	public function buildModerationButtons()
959
	{
960
		global $context, $txt;
961
962
		// Build the mod button array
963
		$context['mod_buttons'] = array(
964
			'move' => array(
965
				'test' => 'can_move',
966
				'text' => 'move_topic',
967
				'lang' => true,
968
				'url' => getUrl('action', ['action' => 'movetopic', 'current_board' => $context['current_board'], 'topic' => $context['current_topic'] . '.0'])
969
			),
970
			'delete' => array(
971
				'test' => 'can_delete',
972
				'text' => 'remove_topic',
973
				'lang' => true,
974
				'custom' => 'onclick="return confirm(\'' . $txt['are_sure_remove_topic'] . '\');"',
975
				'url' => getUrl('action', ['action' => 'removetopic2', 'topic' => $context['current_topic'] . '.0', '{session_data}'])
976
			),
977
			'lock' => array(
978
				'test' => 'can_lock',
979
				'text' => empty($this->topicinfo['locked']) ? 'set_lock' : 'set_unlock',
980
				'lang' => true,
981
				'url' => getUrl('action', ['action' => 'topic', 'sa' => 'lock', 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}'])
982
			),
983
			'sticky' => array(
984
				'test' => 'can_sticky',
985
				'text' => empty($this->topicinfo['is_sticky']) ? 'set_sticky' : 'set_nonsticky',
986
				'lang' => true,
987
				'url' => getUrl('action', ['action' => 'topic', 'sa' => 'sticky', 'topic' => $context['current_topic'] . '.' . $context['start'], '{session_data}'])
988
			),
989
			'merge' => array(
990
				'test' => 'can_merge',
991
				'text' => 'merge',
992
				'lang' => true,
993
				'url' => getUrl('action', ['action' => 'mergetopics', 'board' => $context['current_board'] . '.0', 'from' => $context['current_topic']])
994
			),
995
		);
996
997
		// Restore topic. eh?  No monkey business.
998
		if ($context['can_restore_topic'])
999
		{
1000
			$context['mod_buttons'][] = array(
1001
				'text' => 'restore_topic',
1002
				'lang' => true,
1003
				'url' => getUrl('action', ['action' => 'restoretopic', 'topics' => $context['current_topic'], '{session_data}'])
1004
			);
1005
		}
1006
1007
		// Allow adding new buttons easily.
1008
		call_integration_hook('integrate_mod_buttons');
1009
	}
1010
1011
	/**
1012
	 * If we are in a topic and don't have permission to approve it then duck out now.
1013
	 * This is an abuse of the method, but it's easier that way.
1014
	 *
1015
	 * @param string $action the function name of the current action
1016
	 *
1017
	 * @return bool
1018
	 * @throws \ElkArte\Exceptions\Exception not_a_topic
1019
	 */
1020
	public function trackStats($action = '')
1021
	{
1022
		global $topic, $board_info;
1023
1024
		if (!empty($topic)
1025
			&& empty($board_info['cur_topic_approved'])
1026
			&& ($this->user->id != $board_info['cur_topic_starter'] || $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...
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...
1027
			&& !allowedTo('approve_posts'))
1028
		{
1029
			throw new Exception('not_a_topic', false);
1030
		}
1031
1032
		return parent::trackStats($action);
1033
	}
1034
1035
	/**
1036
	 * In-topic quick moderation.
1037
	 *
1038
	 * Accessed by ?action=quickmod2 from quickModForm
1039
	 */
1040
	public function action_quickmod2()
1041
	{
1042
		global $topic, $board, $context, $modSettings;
1043
1044
		// Check the session = get or post.
1045
		checkSession('request');
1046
1047
		require_once(SUBSDIR . '/Messages.subs.php');
1048
1049
		if (empty($this->_req->post->msgs))
1050
		{
1051
			redirectexit('topic=' . $topic . '.' . $this->_req->getQuery('start', 'intval'));
1052
		}
1053
1054
		$messages = array_map('intval', $this->_req->post->msgs);
1055
1056
		// We are restoring messages. We handle this in another place.
1057
		if (isset($this->_req->query->restore_selected))
1058
		{
1059
			redirectexit('action=restoretopic;msgs=' . implode(',', $messages) . ';' . $context['session_var'] . '=' . $context['session_id']);
1060
		}
1061
1062
		if (isset($this->_req->query->split_selection))
1063
		{
1064
			$mgsOptions = basicMessageInfo(min($messages), true);
1065
1066
			$_SESSION['split_selection'][$topic] = $messages;
1067
			redirectexit('action=splittopics;sa=selectTopics;topic=' . $topic . '.0;subname_enc=' . urlencode($mgsOptions['subject']) . ';' . $context['session_var'] . '=' . $context['session_id']);
1068
		}
1069
1070
		require_once(SUBSDIR . '/Topic.subs.php');
1071
		$topic_info = getTopicInfo($topic);
1072
1073
		// Allowed to delete any message?
1074
		$allowed_all = $this->canDeleteAll($topic_info);
1075
1076
		// Make sure they're allowed to delete their own messages, if not any.
1077
		if (!$allowed_all)
1078
		{
1079
			isAllowedTo('delete_own');
1080
		}
1081
1082
		// Allowed to remove which messages?
1083
		$messages = determineRemovableMessages($topic, $messages, $allowed_all);
1084
1085
		// Get the first message in the topic - because you can't delete that!
1086
		$first_message = (int) $topic_info['id_first_msg'];
1087
		$last_message = (int) $topic_info['id_last_msg'];
1088
		$remover = new MessagesDelete($modSettings['recycle_enable'], $modSettings['recycle_board']);
1089
1090
		// Delete all the messages we know they can delete. ($messages)
1091
		foreach ($messages as $message => $info)
1092
		{
1093
			$message = (int) $message;
1094
1095
			// Just skip the first message - if it's not the last.
1096
			if ($message === $first_message && $message !== $last_message)
1097
			{
1098
				continue;
1099
			}
1100
1101
			// If the first message is going then don't bother going back to the topic as we're effectively deleting it.
1102
			if ($message === $first_message)
1103
			{
1104
				$topicGone = true;
1105
			}
1106
1107
			$remover->removeMessage($message);
1108
		}
1109
1110
		redirectexit(empty($topicGone) ? 'topic=' . $topic . '.' . (int) $this->_req->query->start : 'board=' . $board);
1111
	}
1112
1113
	/**
1114
	 * Determine if this user can delete all replies in this message
1115
	 *
1116
	 * @param array $topic_info
1117
	 * @return bool
1118
	 */
1119
	public function canDeleteAll($topic_info)
1120
	{
1121
		if (allowedTo('delete_any'))
1122
		{
1123
			return true;
1124
		}
1125
1126
		// Allowed to delete replies to their messages?
1127
		if (allowedTo('delete_replies'))
1128
		{
1129
			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...
1130
		}
1131
1132
		return false;
1133
	}
1134
}
1135