Display   F
last analyzed

Complexity

Total Complexity 163

Size/Duplication

Total Lines 1109
Duplicated Lines 0 %

Test Coverage

Coverage 75.13%

Importance

Changes 0
Metric Value
eloc 456
dl 0
loc 1109
rs 2
c 0
b 0
f 0
ccs 284
cts 378
cp 0.7513
wmc 163

26 Methods

Rating   Name   Duplication   Size   Complexity  
A action_index() 0 4 1
A getCanShowAll() 0 7 3
A setMessagesPerPage() 0 5 3
A didThisUserStart() 0 3 2
A warnOldTopic() 0 17 5
A markNotificationsRead() 0 19 3
A buildNormalButtons() 0 47 5
F setTopicCanPermissions() 0 65 28
B setupShowAll() 0 37 7
A loadTopicInfo() 0 20 2
A trackStats() 0 13 6
A setSignatureShowStatus() 0 13 4
A buildModerationButtons() 0 51 4
A canDeleteAll() 0 14 3
B getVisiblePosts() 0 15 8
B markRead() 0 36 8
A getIncludeUnapproved() 0 5 2
C makeStartAdjustments() 0 41 13
A getPreviousNextTopic() 0 21 5
A prepareQuickReply() 0 13 3
B setMessageContext() 0 86 8
A setRobotNoIndex() 0 16 6
B action_quickmod2() 0 71 10
A handleRedirection() 0 20 5
A increaseTopicViews() 0 7 4
F action_display() 0 235 15

How to fix   Complexity   

Complex Class

Complex classes like Display often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Display, and based on these observations, apply Extract Interface, too.

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