Completed
Push — release-2.1 ( 31dcfd...75561c )
by Mert
06:37
created

Display.php ➔ Display()   F

Complexity

Conditions 308
Paths 0

Size

Total Lines 1292
Code Lines 682

Duplication

Lines 242
Ratio 18.73 %

Importance

Changes 0
Metric Value
cc 308
eloc 682
nc 0
nop 0
dl 242
loc 1292
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This is perhaps the most important and probably most accessed file in all
5
 * of SMF.  This file controls topic, message, and attachment display.
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines http://www.simplemachines.org
11
 * @copyright 2016 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 3
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/**
21
 * The central part of the board - topic display.
22
 * This function loads the posts in a topic up so they can be displayed.
23
 * It uses the main sub template of the Display template.
24
 * It requires a topic, and can go to the previous or next topic from it.
25
 * It jumps to the correct post depending on a number/time/IS_MSG passed.
26
 * It depends on the messages_per_page, defaultMaxMessages and enableAllMessages settings.
27
 * It is accessed by ?topic=id_topic.START.
28
 * @return void
29
 */
30
function Display()
31
{
32
	global $scripturl, $txt, $modSettings, $context, $settings;
33
	global $options, $sourcedir, $user_info, $board_info, $topic, $board;
34
	global $attachments, $messages_request, $language, $smcFunc;
35
36
	// What are you gonna display if these are empty?!
37
	if (empty($topic))
38
		fatal_lang_error('no_board', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
39
40
	// Load the proper template.
41
	loadTemplate('Display');
42
43
	// Not only does a prefetch make things slower for the server, but it makes it impossible to know if they read it.
44
	if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch')
45
	{
46
		ob_end_clean();
47
		header('HTTP/1.1 403 Prefetch Forbidden');
48
		die;
49
	}
50
51
	// How much are we sticking on each page?
52
	$context['messages_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
53
54
	// Let's do some work on what to search index.
55 View Code Duplication
	if (count($_GET) > 2)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
56
		foreach ($_GET as $k => $v)
57
		{
58
			if (!in_array($k, array('topic', 'board', 'start', session_name())))
59
				$context['robot_no_index'] = true;
60
		}
61
62 View Code Duplication
	if (!empty($_REQUEST['start']) && (!is_numeric($_REQUEST['start']) || $_REQUEST['start'] % $context['messages_per_page'] != 0))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
63
		$context['robot_no_index'] = true;
64
65
	// Find the previous or next topic.  Make a fuss if there are no more.
66
	if (isset($_REQUEST['prev_next']) && ($_REQUEST['prev_next'] == 'prev' || $_REQUEST['prev_next'] == 'next'))
67
	{
68
		// No use in calculating the next topic if there's only one.
69
		if ($board_info['num_topics'] > 1)
70
		{
71
			// Just prepare some variables that are used in the query.
72
			$gt_lt = $_REQUEST['prev_next'] == 'prev' ? '>' : '<';
73
			$order = $_REQUEST['prev_next'] == 'prev' ? '' : ' DESC';
74
75
			$request = $smcFunc['db_query']('', '
76
				SELECT t2.id_topic
77
				FROM {db_prefix}topics AS t
78
					INNER JOIN {db_prefix}topics AS t2 ON (
79
					(t2.id_last_msg ' . $gt_lt . ' t.id_last_msg AND t2.is_sticky ' . $gt_lt . '= t.is_sticky) OR t2.is_sticky ' . $gt_lt . ' t.is_sticky)
80
				WHERE t.id_topic = {int:current_topic}
81
					AND t2.id_board = {int:current_board}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
82
					AND (t2.approved = {int:is_approved} OR (t2.id_member_started != {int:id_member_started} AND t2.id_member_started = {int:current_member}))') . '
83
				ORDER BY t2.is_sticky' . $order . ', t2.id_last_msg' . $order . '
84
				LIMIT 1',
85
				array(
86
					'current_board' => $board,
87
					'current_member' => $user_info['id'],
88
					'current_topic' => $topic,
89
					'is_approved' => 1,
90
					'id_member_started' => 0,
91
				)
92
			);
93
94
			// No more left.
95
			if ($smcFunc['db_num_rows']($request) == 0)
96
			{
97
				$smcFunc['db_free_result']($request);
98
99
				// Roll over - if we're going prev, get the last - otherwise the first.
100
				$request = $smcFunc['db_query']('', '
101
					SELECT id_topic
102
					FROM {db_prefix}topics
103
					WHERE id_board = {int:current_board}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
104
						AND (approved = {int:is_approved} OR (id_member_started != {int:id_member_started} AND id_member_started = {int:current_member}))') . '
105
					ORDER BY is_sticky' . $order . ', id_last_msg' . $order . '
106
					LIMIT 1',
107
					array(
108
						'current_board' => $board,
109
						'current_member' => $user_info['id'],
110
						'is_approved' => 1,
111
						'id_member_started' => 0,
112
					)
113
				);
114
			}
115
116
			// Now you can be sure $topic is the id_topic to view.
117
			list ($topic) = $smcFunc['db_fetch_row']($request);
118
			$smcFunc['db_free_result']($request);
119
120
			$context['current_topic'] = $topic;
121
		}
122
123
		// Go to the newest message on this topic.
124
		$_REQUEST['start'] = 'new';
125
	}
126
127
	// Add 1 to the number of views of this topic (except for robots).
128
	if (!$user_info['possibly_robot'] && (empty($_SESSION['last_read_topic']) || $_SESSION['last_read_topic'] != $topic))
129
	{
130
		$smcFunc['db_query']('', '
131
			UPDATE {db_prefix}topics
132
			SET num_views = num_views + 1
133
			WHERE id_topic = {int:current_topic}',
134
			array(
135
				'current_topic' => $topic,
136
			)
137
		);
138
139
		$_SESSION['last_read_topic'] = $topic;
140
	}
141
142
	$topic_parameters = array(
143
		'current_member' => $user_info['id'],
144
		'current_topic' => $topic,
145
		'current_board' => $board,
146
	);
147
	$topic_selects = array();
148
	$topic_tables = array();
149
	$context['topicinfo'] = array();
150
	call_integration_hook('integrate_display_topic', array(&$topic_selects, &$topic_tables, &$topic_parameters));
151
152
	// @todo Why isn't this cached?
153
	// @todo if we get id_board in this query and cache it, we can save a query on posting
154
	// Get all the important topic info.
155
	$request = $smcFunc['db_query']('', '
156
		SELECT
157
			t.num_replies, t.num_views, t.locked, ms.subject, t.is_sticky, t.id_poll,
158
			t.id_member_started, t.id_first_msg, t.id_last_msg, t.approved, t.unapproved_posts, t.id_redirect_topic,
159
			COALESCE(mem.real_name, ms.poster_name) AS topic_started_name, ms.poster_time AS topic_started_time,
160
			' . ($user_info['is_guest'] ? 't.id_last_msg + 1' : 'COALESCE(lt.id_msg, lmr.id_msg, -1) + 1') . ' AS new_from
161
			' . (!empty($board_info['recycle']) ? ', id_previous_board, id_previous_topic' : '') . '
162
			' . (!empty($topic_selects) ? (', '. implode(', ', $topic_selects)) : '') . '
163
			' . (!$user_info['is_guest'] ? ', COALESCE(lt.unwatched, 0) as unwatched' : '') . '
164
		FROM {db_prefix}topics AS t
165
			INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)
166
			LEFT JOIN {db_prefix}members AS mem on (mem.id_member = ms.id_member)' . ($user_info['is_guest'] ? '' : '
167
			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = {int:current_topic} AND lt.id_member = {int:current_member})
168
			LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})') . '
169
			' . (!empty($topic_tables) ? implode("\n\t", $topic_tables) : '') . '
170
		WHERE t.id_topic = {int:current_topic}
171
		LIMIT 1',
172
			$topic_parameters
173
	);
174
175
	if ($smcFunc['db_num_rows']($request) == 0)
176
		fatal_lang_error('not_a_topic', false, 404);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
404 is of type integer, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
177
	$context['topicinfo'] = $smcFunc['db_fetch_assoc']($request);
178
	$smcFunc['db_free_result']($request);
179
180
	// Is this a moved or merged topic that we are redirecting to?
181
	if (!empty($context['topicinfo']['id_redirect_topic']))
182
	{
183
		// Mark this as read...
184
		if (!$user_info['is_guest'] && $context['topicinfo']['new_from'] != $context['topicinfo']['id_first_msg'])
185
		{
186
			// Mark this as read first
187
			$smcFunc['db_insert']($context['topicinfo']['new_from'] == 0 ? 'ignore' : 'replace',
188
				'{db_prefix}log_topics',
189
				array(
190
					'id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'unwatched' => 'int',
191
				),
192
				array(
193
					$user_info['id'], $topic, $context['topicinfo']['id_first_msg'], $context['topicinfo']['unwatched'],
194
				),
195
				array('id_member', 'id_topic')
196
			);
197
		}
198
		redirectexit('topic=' . $context['topicinfo']['id_redirect_topic'] . '.0', false, true);
199
	}
200
201
	// Short-cut to know if this user can see unapproved messages.
202
	$approve_posts = (allowedTo('approve_posts') || $context['topicinfo']['id_member_started'] == $user_info['id']);
203
204
	$context['real_num_replies'] = $context['num_replies'] = $context['topicinfo']['num_replies'];
205
	$context['topic_started_time'] = timeformat($context['topicinfo']['topic_started_time']);
206
	$context['topic_started_timestamp'] = $context['topicinfo']['topic_started_time'];
207
	$context['topic_poster_name'] = $context['topicinfo']['topic_started_name'];
208
	$context['topic_first_message'] = $context['topicinfo']['id_first_msg'];
209
	$context['topic_last_message'] = $context['topicinfo']['id_last_msg'];
210
	$context['topic_unwatched'] = isset($context['topicinfo']['unwatched']) ? $context['topicinfo']['unwatched'] : 0;
211
212
	// Add up unapproved replies to get real number of replies...
213
	if ($modSettings['postmod_active'] && $approve_posts)
214
		$context['real_num_replies'] += $context['topicinfo']['unapproved_posts'] - ($context['topicinfo']['approved'] ? 0 : 1);
215
216
	// If this topic has unapproved posts, we need to work out how many posts the user can see, for page indexing.
217
	if ($modSettings['postmod_active'] && $context['topicinfo']['unapproved_posts'] && !$user_info['is_guest'] && !$approve_posts)
218
	{
219
		$request = $smcFunc['db_query']('', '
220
			SELECT COUNT(id_member) AS my_unapproved_posts
221
			FROM {db_prefix}messages
222
			WHERE id_topic = {int:current_topic}
223
				AND id_member = {int:current_member}
224
				AND approved = 0',
225
			array(
226
				'current_topic' => $topic,
227
				'current_member' => $user_info['id'],
228
			)
229
		);
230
		list ($myUnapprovedPosts) = $smcFunc['db_fetch_row']($request);
231
		$smcFunc['db_free_result']($request);
232
233
		$context['total_visible_posts'] = $context['num_replies'] + $myUnapprovedPosts + ($context['topicinfo']['approved'] ? 1 : 0);
234
	}
235 View Code Duplication
	elseif ($user_info['is_guest'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
236
		$context['total_visible_posts'] = $context['num_replies'] + ($context['topicinfo']['approved'] ? 1 : 0);
237 View Code Duplication
	else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
238
		$context['total_visible_posts'] = $context['num_replies'] + $context['topicinfo']['unapproved_posts'] + ($context['topicinfo']['approved'] ? 1 : 0);
239
240
	// The start isn't a number; it's information about what to do, where to go.
241
	if (!is_numeric($_REQUEST['start']))
242
	{
243
		// Redirect to the page and post with new messages, originally by Omar Bazavilvazo.
244
		if ($_REQUEST['start'] == 'new')
245
		{
246
			// Guests automatically go to the last post.
247
			if ($user_info['is_guest'])
248
			{
249
				$context['start_from'] = $context['total_visible_posts'] - 1;
250
				$_REQUEST['start'] = empty($options['view_newest_first']) ? $context['start_from'] : 0;
251
			}
252 View Code Duplication
			else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
			{
254
				// Find the earliest unread message in the topic. (the use of topics here is just for both tables.)
255
				$request = $smcFunc['db_query']('', '
256
					SELECT COALESCE(lt.id_msg, lmr.id_msg, -1) + 1 AS new_from
257
					FROM {db_prefix}topics AS t
258
						LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = {int:current_topic} AND lt.id_member = {int:current_member})
259
						LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})
260
					WHERE t.id_topic = {int:current_topic}
261
					LIMIT 1',
262
					array(
263
						'current_board' => $board,
264
						'current_member' => $user_info['id'],
265
						'current_topic' => $topic,
266
					)
267
				);
268
				list ($new_from) = $smcFunc['db_fetch_row']($request);
269
				$smcFunc['db_free_result']($request);
270
271
				// Fall through to the next if statement.
272
				$_REQUEST['start'] = 'msg' . $new_from;
273
			}
274
		}
275
276
		// Start from a certain time index, not a message.
277
		if (substr($_REQUEST['start'], 0, 4) == 'from')
278
		{
279
			$timestamp = (int) substr($_REQUEST['start'], 4);
280
			if ($timestamp === 0)
281
				$_REQUEST['start'] = 0;
282
			else
283
			{
284
				// Find the number of messages posted before said time...
285
				$request = $smcFunc['db_query']('', '
286
					SELECT COUNT(*)
287
					FROM {db_prefix}messages
288
					WHERE poster_time < {int:timestamp}
289
						AND id_topic = {int:current_topic}' . ($modSettings['postmod_active'] && $context['topicinfo']['unapproved_posts'] && !allowedTo('approve_posts') ? '
290
						AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')' : ''),
291
					array(
292
						'current_topic' => $topic,
293
						'current_member' => $user_info['id'],
294
						'is_approved' => 1,
295
						'timestamp' => $timestamp,
296
					)
297
				);
298
				list ($context['start_from']) = $smcFunc['db_fetch_row']($request);
299
				$smcFunc['db_free_result']($request);
300
301
				// Handle view_newest_first options, and get the correct start value.
302
				$_REQUEST['start'] = empty($options['view_newest_first']) ? $context['start_from'] : $context['total_visible_posts'] - $context['start_from'] - 1;
303
			}
304
		}
305
306
		// Link to a message...
307
		elseif (substr($_REQUEST['start'], 0, 3) == 'msg')
308
		{
309
			$virtual_msg = (int) substr($_REQUEST['start'], 3);
310
			if (!$context['topicinfo']['unapproved_posts'] && $virtual_msg >= $context['topicinfo']['id_last_msg'])
311
				$context['start_from'] = $context['total_visible_posts'] - 1;
312
			elseif (!$context['topicinfo']['unapproved_posts'] && $virtual_msg <= $context['topicinfo']['id_first_msg'])
313
				$context['start_from'] = 0;
314
			else
315
			{
316
				// Find the start value for that message......
317
				$request = $smcFunc['db_query']('', '
318
					SELECT COUNT(*)
319
					FROM {db_prefix}messages
320
					WHERE id_msg < {int:virtual_msg}
321
						AND id_topic = {int:current_topic}' . ($modSettings['postmod_active'] && $context['topicinfo']['unapproved_posts'] && !allowedTo('approve_posts') ? '
322
						AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')' : ''),
323
					array(
324
						'current_member' => $user_info['id'],
325
						'current_topic' => $topic,
326
						'virtual_msg' => $virtual_msg,
327
						'is_approved' => 1,
328
						'no_member' => 0,
329
					)
330
				);
331
				list ($context['start_from']) = $smcFunc['db_fetch_row']($request);
332
				$smcFunc['db_free_result']($request);
333
			}
334
335
			// We need to reverse the start as well in this case.
336
			$_REQUEST['start'] = empty($options['view_newest_first']) ? $context['start_from'] : $context['total_visible_posts'] - $context['start_from'] - 1;
337
		}
338
	}
339
340
	// Create a previous next string if the selected theme has it as a selected option.
341
	$context['previous_next'] = $modSettings['enablePreviousNext'] ? '<a href="' . $scripturl . '?topic=' . $topic . '.0;prev_next=prev#new">' . $txt['previous_next_back'] . '</a> - <a href="' . $scripturl . '?topic=' . $topic . '.0;prev_next=next#new">' . $txt['previous_next_forward'] . '</a>' : '';
342
343
	// Check if spellchecking is both enabled and actually working. (for quick reply.)
344
	$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && (function_exists('pspell_new') || (function_exists('enchant_broker_init') && ($txt['lang_charset'] == 'UTF-8' || function_exists('iconv'))));
345
346
	// Do we need to show the visual verification image?
347
	$context['require_verification'] = !$user_info['is_mod'] && !$user_info['is_admin'] && !empty($modSettings['posts_require_captcha']) && ($user_info['posts'] < $modSettings['posts_require_captcha'] || ($user_info['is_guest'] && $modSettings['posts_require_captcha'] == -1));
348 View Code Duplication
	if ($context['require_verification'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
349
	{
350
		require_once($sourcedir . '/Subs-Editor.php');
351
		$verificationOptions = array(
352
			'id' => 'post',
353
		);
354
		$context['require_verification'] = create_control_verification($verificationOptions);
355
		$context['visual_verification_id'] = $verificationOptions['id'];
356
	}
357
358
	// Are we showing signatures - or disabled fields?
359
	$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
360
	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
361
362
	// Censor the title...
363
	censorText($context['topicinfo']['subject']);
364
	$context['page_title'] = $context['topicinfo']['subject'];
365
366
	// Default this topic to not marked for notifications... of course...
367
	$context['is_marked_notify'] = false;
368
369
	// Did we report a post to a moderator just now?
370
	$context['report_sent'] = isset($_GET['reportsent']);
371
372
	// Let's get nosey, who is viewing this topic?
373 View Code Duplication
	if (!empty($settings['display_who_viewing']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
374
	{
375
		// Start out with no one at all viewing it.
376
		$context['view_members'] = array();
377
		$context['view_members_list'] = array();
378
		$context['view_num_hidden'] = 0;
379
380
		// Search for members who have this topic set in their GET data.
381
		$request = $smcFunc['db_query']('', '
382
			SELECT
383
				lo.id_member, lo.log_time, mem.real_name, mem.member_name, mem.show_online,
384
				mg.online_color, mg.id_group, mg.group_name
385
			FROM {db_prefix}log_online AS lo
386
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lo.id_member)
387
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_id_group} THEN mem.id_post_group ELSE mem.id_group END)
388
			WHERE INSTR(lo.url, {string:in_url_string}) > 0 OR lo.session = {string:session}',
389
			array(
390
				'reg_id_group' => 0,
391
				'in_url_string' => '"topic":'.$topic,
392
				'session' => $user_info['is_guest'] ? 'ip' . $user_info['ip'] : session_id(),
393
			)
394
		);
395
		while ($row = $smcFunc['db_fetch_assoc']($request))
396
		{
397
			if (empty($row['id_member']))
398
				continue;
399
400
			if (!empty($row['online_color']))
401
				$link = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '" style="color: ' . $row['online_color'] . ';">' . $row['real_name'] . '</a>';
402
			else
403
				$link = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
404
405
			$is_buddy = in_array($row['id_member'], $user_info['buddies']);
406
			if ($is_buddy)
407
				$link = '<strong>' . $link . '</strong>';
408
409
			// Add them both to the list and to the more detailed list.
410
			if (!empty($row['show_online']) || allowedTo('moderate_forum'))
411
				$context['view_members_list'][$row['log_time'] . $row['member_name']] = empty($row['show_online']) ? '<em>' . $link . '</em>' : $link;
412
			$context['view_members'][$row['log_time'] . $row['member_name']] = array(
413
				'id' => $row['id_member'],
414
				'username' => $row['member_name'],
415
				'name' => $row['real_name'],
416
				'group' => $row['id_group'],
417
				'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
418
				'link' => $link,
419
				'is_buddy' => $is_buddy,
420
				'hidden' => empty($row['show_online']),
421
			);
422
423
			if (empty($row['show_online']))
424
				$context['view_num_hidden']++;
425
		}
426
427
		// The number of guests is equal to the rows minus the ones we actually used ;).
428
		$context['view_num_guests'] = $smcFunc['db_num_rows']($request) - count($context['view_members']);
429
		$smcFunc['db_free_result']($request);
430
431
		// Sort the list.
432
		krsort($context['view_members']);
433
		krsort($context['view_members_list']);
434
	}
435
436
	// If all is set, but not allowed... just unset it.
437
	$can_show_all = !empty($modSettings['enableAllMessages']) && $context['total_visible_posts'] > $context['messages_per_page'] && $context['total_visible_posts'] < $modSettings['enableAllMessages'];
438
	if (isset($_REQUEST['all']) && !$can_show_all)
439
		unset($_REQUEST['all']);
440
	// Otherwise, it must be allowed... so pretend start was -1.
441
	elseif (isset($_REQUEST['all']))
442
		$_REQUEST['start'] = -1;
443
444
	// Construct the page index, allowing for the .START method...
445
	$context['page_index'] = constructPageIndex($scripturl . '?topic=' . $topic . '.%1$d', $_REQUEST['start'], $context['total_visible_posts'], $context['messages_per_page'], true);
446
	$context['start'] = $_REQUEST['start'];
447
448
	// This is information about which page is current, and which page we're on - in case you don't like the constructed page index. (again, wireles..)
449
	$context['page_info'] = array(
450
		'current_page' => $_REQUEST['start'] / $context['messages_per_page'] + 1,
451
		'num_pages' => floor(($context['total_visible_posts'] - 1) / $context['messages_per_page']) + 1,
452
	);
453
454
	// Figure out all the link to the next/prev/first/last/etc.
455
	if (!($can_show_all && isset($_REQUEST['all'])))
456
	{
457
		$context['links'] = array(
458
			'first' => $_REQUEST['start'] >= $context['messages_per_page'] ? $scripturl . '?topic=' . $topic . '.0' : '',
459
			'prev' => $_REQUEST['start'] >= $context['messages_per_page'] ? $scripturl . '?topic=' . $topic . '.' . ($_REQUEST['start'] - $context['messages_per_page']) : '',
460
			'next' => $_REQUEST['start'] + $context['messages_per_page'] < $context['total_visible_posts'] ? $scripturl . '?topic=' . $topic. '.' . ($_REQUEST['start'] + $context['messages_per_page']) : '',
461
			'last' => $_REQUEST['start'] + $context['messages_per_page'] < $context['total_visible_posts'] ? $scripturl . '?topic=' . $topic. '.' . (floor($context['total_visible_posts'] / $context['messages_per_page']) * $context['messages_per_page']) : '',
462
			'up' => $scripturl . '?board=' . $board . '.0'
463
		);
464
	}
465
466
	// If they are viewing all the posts, show all the posts, otherwise limit the number.
467
	if ($can_show_all)
468
	{
469
		if (isset($_REQUEST['all']))
470
		{
471
			// No limit! (actually, there is a limit, but...)
472
			$context['messages_per_page'] = -1;
473
			$context['page_index'] .= empty($modSettings['compactTopicPagesEnable']) ? '<strong>' . $txt['all'] . '</strong> ' : '[<strong>' . $txt['all'] . '</strong>] ';
474
475
			// Set start back to 0...
476
			$_REQUEST['start'] = 0;
477
		}
478
		// They aren't using it, but the *option* is there, at least.
479
		else
480
			$context['page_index'] .= '&nbsp;<a href="' . $scripturl . '?topic=' . $topic . '.0;all">' . $txt['all'] . '</a> ';
481
	}
482
483
	// Build the link tree.
484
	$context['linktree'][] = array(
485
		'url' => $scripturl . '?topic=' . $topic . '.0',
486
		'name' => $context['topicinfo']['subject'],
487
	);
488
489
	// Build a list of this board's moderators.
490
	$context['moderators'] = &$board_info['moderators'];
491
	$context['moderator_groups'] = &$board_info['moderator_groups'];
492
	$context['link_moderators'] = array();
493 View Code Duplication
	if (!empty($board_info['moderators']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
494
	{
495
		// Add a link for each moderator...
496
		foreach ($board_info['moderators'] as $mod)
497
			$context['link_moderators'][] = '<a href="' . $scripturl . '?action=profile;u=' . $mod['id'] . '" title="' . $txt['board_moderator'] . '">' . $mod['name'] . '</a>';
498
	}
499 View Code Duplication
	if (!empty($board_info['moderator_groups']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
500
	{
501
		// Add a link for each moderator group as well...
502
		foreach ($board_info['moderator_groups'] as $mod_group)
503
			$context['link_moderators'][] = '<a href="' . $scripturl . '?action=groups;sa=viewmemberes;group=' . $mod_group['id'] . '" title="' . $txt['board_moderator'] . '">' . $mod_group['name'] . '</a>';
504
	}
505
506 View Code Duplication
	if (!empty($context['link_moderators']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
507
	{
508
		// And show it after the board's name.
509
		$context['linktree'][count($context['linktree']) - 2]['extra_after'] = '<span class="board_moderators">(' . (count($context['link_moderators']) == 1 ? $txt['moderator'] : $txt['moderators']) . ': ' . implode(', ', $context['link_moderators']) . ')</span>';
510
	}
511
512
	// Information about the current topic...
513
	$context['is_locked'] = $context['topicinfo']['locked'];
514
	$context['is_sticky'] = $context['topicinfo']['is_sticky'];
515
	$context['is_approved'] = $context['topicinfo']['approved'];
516
	$context['is_poll'] = $context['topicinfo']['id_poll'] > 0 && $modSettings['pollMode'] == '1' && allowedTo('poll_view');
517
518
	// Did this user start the topic or not?
519
	$context['user']['started'] = $user_info['id'] == $context['topicinfo']['id_member_started'] && !$user_info['is_guest'];
520
	$context['topic_starter_id'] = $context['topicinfo']['id_member_started'];
521
522
	// Set the topic's information for the template.
523
	$context['subject'] = $context['topicinfo']['subject'];
524
	$context['num_views'] = comma_format($context['topicinfo']['num_views']);
525
	$context['num_views_text'] = $context['num_views'] == 1 ? $txt['read_one_time'] : sprintf($txt['read_many_times'], $context['num_views']);
526
	$context['mark_unread_time'] = !empty($virtual_msg) ? $virtual_msg : $context['topicinfo']['new_from'];
527
528
	// Set a canonical URL for this page.
529
	$context['canonical_url'] = $scripturl . '?topic=' . $topic . '.' . ($can_show_all ? '0;all' : $context['start']);
530
531
	// For quick reply we need a response prefix in the default forum language.
532 View Code Duplication
	if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix', 600)))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
533
	{
534
		if ($language === $user_info['language'])
535
			$context['response_prefix'] = $txt['response_prefix'];
536
		else
537
		{
538
			loadLanguage('index', $language, false);
539
			$context['response_prefix'] = $txt['response_prefix'];
540
			loadLanguage('index');
541
		}
542
		cache_put_data('response_prefix', $context['response_prefix'], 600);
543
	}
544
545
	// If we want to show event information in the topic, prepare the data.
546
	if (allowedTo('calendar_view') && !empty($modSettings['cal_showInTopic']) && !empty($modSettings['cal_enabled']))
547
	{
548
		// First, try create a better time format, ignoring the "time" elements.
549 View Code Duplication
		if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
550
			$date_string = $user_info['time_format'];
551
		else
552
			$date_string = $matches[0];
553
554
		// Any calendar information for this topic?
555
		$request = $smcFunc['db_query']('', '
556
			SELECT cal.id_event, cal.start_date, cal.end_date, cal.title, cal.id_member, mem.real_name
557
			FROM {db_prefix}calendar AS cal
558
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = cal.id_member)
559
			WHERE cal.id_topic = {int:current_topic}
560
			ORDER BY start_date',
561
			array(
562
				'current_topic' => $topic,
563
			)
564
		);
565
		$context['linked_calendar_events'] = array();
566
		while ($row = $smcFunc['db_fetch_assoc']($request))
567
		{
568
			// Prepare the dates for being formatted.
569
			$start_date = sscanf($row['start_date'], '%04d-%02d-%02d');
570
			$start_date = mktime(12, 0, 0, $start_date[1], $start_date[2], $start_date[0]);
571
			$end_date = sscanf($row['end_date'], '%04d-%02d-%02d');
572
			$end_date = mktime(12, 0, 0, $end_date[1], $end_date[2], $end_date[0]);
573
574
			$context['linked_calendar_events'][] = array(
575
				'id' => $row['id_event'],
576
				'title' => $row['title'],
577
				'can_edit' => allowedTo('calendar_edit_any') || ($row['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own')),
578
				'modify_href' => $scripturl . '?action=post;msg=' . $context['topicinfo']['id_first_msg'] . ';topic=' . $topic . '.0;calendar;eventid=' . $row['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'],
579
				'can_export' => allowedTo('calendar_edit_any') || ($row['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own')),
580
				'export_href' => $scripturl . '?action=calendar;sa=ical;eventid=' . $row['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'],
581
				'start_date' => timeformat($start_date, $date_string, 'none'),
582
				'start_timestamp' => $start_date,
583
				'end_date' => timeformat($end_date, $date_string, 'none'),
584
				'end_timestamp' => $end_date,
585
				'is_last' => false
586
			);
587
		}
588
		$smcFunc['db_free_result']($request);
589
590 View Code Duplication
		if (!empty($context['linked_calendar_events']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
591
			$context['linked_calendar_events'][count($context['linked_calendar_events']) - 1]['is_last'] = true;
592
	}
593
594
	// Create the poll info if it exists.
595
	if ($context['is_poll'])
596
	{
597
		// Get the question and if it's locked.
598
		$request = $smcFunc['db_query']('', '
599
			SELECT
600
				p.question, p.voting_locked, p.hide_results, p.expire_time, p.max_votes, p.change_vote,
601
				p.guest_vote, p.id_member, COALESCE(mem.real_name, p.poster_name) AS poster_name, p.num_guest_voters, p.reset_poll
602
			FROM {db_prefix}polls AS p
603
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = p.id_member)
604
			WHERE p.id_poll = {int:id_poll}
605
			LIMIT 1',
606
			array(
607
				'id_poll' => $context['topicinfo']['id_poll'],
608
			)
609
		);
610
		$pollinfo = $smcFunc['db_fetch_assoc']($request);
611
		$smcFunc['db_free_result']($request);
612
613
		$request = $smcFunc['db_query']('', '
614
			SELECT COUNT(DISTINCT id_member) AS total
615
			FROM {db_prefix}log_polls
616
			WHERE id_poll = {int:id_poll}
617
				AND id_member != {int:not_guest}',
618
			array(
619
				'id_poll' => $context['topicinfo']['id_poll'],
620
				'not_guest' => 0,
621
			)
622
		);
623
		list ($pollinfo['total']) = $smcFunc['db_fetch_row']($request);
624
		$smcFunc['db_free_result']($request);
625
626
		// Total voters needs to include guest voters
627
		$pollinfo['total'] += $pollinfo['num_guest_voters'];
628
629
		// Get all the options, and calculate the total votes.
630
		$request = $smcFunc['db_query']('', '
631
			SELECT pc.id_choice, pc.label, pc.votes, COALESCE(lp.id_choice, -1) AS voted_this
632
			FROM {db_prefix}poll_choices AS pc
633
				LEFT JOIN {db_prefix}log_polls AS lp ON (lp.id_choice = pc.id_choice AND lp.id_poll = {int:id_poll} AND lp.id_member = {int:current_member} AND lp.id_member != {int:not_guest})
634
			WHERE pc.id_poll = {int:id_poll}',
635
			array(
636
				'current_member' => $user_info['id'],
637
				'id_poll' => $context['topicinfo']['id_poll'],
638
				'not_guest' => 0,
639
			)
640
		);
641
		$pollOptions = array();
642
		$realtotal = 0;
643
		$pollinfo['has_voted'] = false;
644 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
645
		{
646
			censorText($row['label']);
647
			$pollOptions[$row['id_choice']] = $row;
648
			$realtotal += $row['votes'];
649
			$pollinfo['has_voted'] |= $row['voted_this'] != -1;
650
		}
651
		$smcFunc['db_free_result']($request);
652
653
		// If this is a guest we need to do our best to work out if they have voted, and what they voted for.
654 View Code Duplication
		if ($user_info['is_guest'] && $pollinfo['guest_vote'] && allowedTo('poll_vote'))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
655
		{
656
			if (!empty($_COOKIE['guest_poll_vote']) && preg_match('~^[0-9,;]+$~', $_COOKIE['guest_poll_vote']) && strpos($_COOKIE['guest_poll_vote'], ';' . $context['topicinfo']['id_poll'] . ',') !== false)
657
			{
658
				// ;id,timestamp,[vote,vote...]; etc
659
				$guestinfo = explode(';', $_COOKIE['guest_poll_vote']);
660
				// Find the poll we're after.
661
				foreach ($guestinfo as $i => $guestvoted)
662
				{
663
					$guestvoted = explode(',', $guestvoted);
664
					if ($guestvoted[0] == $context['topicinfo']['id_poll'])
665
						break;
666
				}
667
				// Has the poll been reset since guest voted?
668
				if ($pollinfo['reset_poll'] > $guestvoted[1])
669
				{
670
					// Remove the poll info from the cookie to allow guest to vote again
671
					unset($guestinfo[$i]);
672
					if (!empty($guestinfo))
673
						$_COOKIE['guest_poll_vote'] = ';' . implode(';', $guestinfo);
674
					else
675
						unset($_COOKIE['guest_poll_vote']);
676
				}
677
				else
678
				{
679
					// What did they vote for?
680
					unset($guestvoted[0], $guestvoted[1]);
681
					foreach ($pollOptions as $choice => $details)
682
					{
683
						$pollOptions[$choice]['voted_this'] = in_array($choice, $guestvoted) ? 1 : -1;
0 ignored issues
show
Bug introduced by
The variable $guestvoted does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
684
						$pollinfo['has_voted'] |= $pollOptions[$choice]['voted_this'] != -1;
685
					}
686
					unset($choice, $details, $guestvoted);
687
				}
688
				unset($guestinfo, $guestvoted, $i);
689
			}
690
		}
691
692
		// Set up the basic poll information.
693
		$context['poll'] = array(
694
			'id' => $context['topicinfo']['id_poll'],
695
			'image' => 'normal_' . (empty($pollinfo['voting_locked']) ? 'poll' : 'locked_poll'),
696
			'question' => parse_bbc($pollinfo['question']),
697
			'total_votes' => $pollinfo['total'],
698
			'change_vote' => !empty($pollinfo['change_vote']),
699
			'is_locked' => !empty($pollinfo['voting_locked']),
700
			'options' => array(),
701
			'lock' => allowedTo('poll_lock_any') || ($context['user']['started'] && allowedTo('poll_lock_own')),
702
			'edit' => allowedTo('poll_edit_any') || ($context['user']['started'] && allowedTo('poll_edit_own')),
703
			'allowed_warning' => $pollinfo['max_votes'] > 1 ? sprintf($txt['poll_options6'], min(count($pollOptions), $pollinfo['max_votes'])) : '',
704
			'is_expired' => !empty($pollinfo['expire_time']) && $pollinfo['expire_time'] < time(),
705
			'expire_time' => !empty($pollinfo['expire_time']) ? timeformat($pollinfo['expire_time']) : 0,
706
			'has_voted' => !empty($pollinfo['has_voted']),
707
			'starter' => array(
708
				'id' => $pollinfo['id_member'],
709
				'name' => $row['poster_name'],
710
				'href' => $pollinfo['id_member'] == 0 ? '' : $scripturl . '?action=profile;u=' . $pollinfo['id_member'],
711
				'link' => $pollinfo['id_member'] == 0 ? $row['poster_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $pollinfo['id_member'] . '">' . $row['poster_name'] . '</a>'
712
			)
713
		);
714
715
		// Make the lock and edit permissions defined above more directly accessible.
716
		$context['allow_lock_poll'] = $context['poll']['lock'];
717
		$context['allow_edit_poll'] = $context['poll']['edit'];
718
719
		// You're allowed to vote if:
720
		// 1. the poll did not expire, and
721
		// 2. you're either not a guest OR guest voting is enabled... and
722
		// 3. you're not trying to view the results, and
723
		// 4. the poll is not locked, and
724
		// 5. you have the proper permissions, and
725
		// 6. you haven't already voted before.
726
		$context['allow_vote'] = !$context['poll']['is_expired'] && (!$user_info['is_guest'] || ($pollinfo['guest_vote'] && allowedTo('poll_vote'))) && empty($pollinfo['voting_locked']) && allowedTo('poll_vote') && !$context['poll']['has_voted'];
727
728
		// You're allowed to view the results if:
729
		// 1. you're just a super-nice-guy, or
730
		// 2. anyone can see them (hide_results == 0), or
731
		// 3. you can see them after you voted (hide_results == 1), or
732
		// 4. you've waited long enough for the poll to expire. (whether hide_results is 1 or 2.)
733
		$context['allow_results_view'] = allowedTo('moderate_board') || $pollinfo['hide_results'] == 0 || ($pollinfo['hide_results'] == 1 && $context['poll']['has_voted']) || $context['poll']['is_expired'];
734
735
		// Show the results if:
736
		// 1. You're allowed to see them (see above), and
737
		// 2. $_REQUEST['viewresults'] or $_REQUEST['viewResults'] is set
738
		$context['poll']['show_results'] = $context['allow_results_view'] && (isset($_REQUEST['viewresults']) || isset($_REQUEST['viewResults']));
739
740
		// Show the button if:
741
		// 1. You can vote in the poll (see above), and
742
		// 2. Results are visible to everyone (hidden = 0), and
743
		// 3. You aren't already viewing the results
744
		$context['show_view_results_button'] = $context['allow_vote'] && $context['allow_results_view'] && !$context['poll']['show_results'];
745
746
		// You're allowed to change your vote if:
747
		// 1. the poll did not expire, and
748
		// 2. you're not a guest... and
749
		// 3. the poll is not locked, and
750
		// 4. you have the proper permissions, and
751
		// 5. you have already voted, and
752
		// 6. the poll creator has said you can!
753
		$context['allow_change_vote'] = !$context['poll']['is_expired'] && !$user_info['is_guest'] && empty($pollinfo['voting_locked']) && allowedTo('poll_vote') && $context['poll']['has_voted'] && $context['poll']['change_vote'];
754
755
		// You're allowed to return to voting options if:
756
		// 1. you are (still) allowed to vote.
757
		// 2. you are currently seeing the results.
758
		$context['allow_return_vote'] = $context['allow_vote'] && $context['poll']['show_results'];
759
760
		// Calculate the percentages and bar lengths...
761
		$divisor = $realtotal == 0 ? 1 : $realtotal;
762
763
		// Determine if a decimal point is needed in order for the options to add to 100%.
764
		$precision = $realtotal == 100 ? 0 : 1;
765
766
		// Now look through each option, and...
767
		foreach ($pollOptions as $i => $option)
768
		{
769
			// First calculate the percentage, and then the width of the bar...
770
			$bar = round(($option['votes'] * 100) / $divisor, $precision);
771
			$barWide = $bar == 0 ? 1 : floor(($bar * 8) / 3);
772
773
			// Now add it to the poll's contextual theme data.
774
			$context['poll']['options'][$i] = array(
775
				'id' => 'options-' . $i,
776
				'percent' => $bar,
777
				'votes' => $option['votes'],
778
				'voted_this' => $option['voted_this'] != -1,
779
				'bar_ndt' => $bar > 0 ? '<div class="bar" style="width: ' . $bar . '%;"></div>' : '',
780
				'bar_width' => $barWide,
781
				'option' => parse_bbc($option['label']),
782
				'vote_button' => '<input type="' . ($pollinfo['max_votes'] > 1 ? 'checkbox' : 'radio') . '" name="options[]" id="options-' . $i . '" value="' . $i . '" class="input_' . ($pollinfo['max_votes'] > 1 ? 'check' : 'radio') . '">'
783
			);
784
		}
785
786
		// Build the poll moderation button array.
787
		$context['poll_buttons'] = array();
788
789 View Code Duplication
		if ($context['allow_return_vote'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
790
			$context['poll_buttons']['vote'] = array('text' => 'poll_return_vote', 'image' => 'poll_options.png', 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start']);
791
792 View Code Duplication
		if ($context['show_view_results_button'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
793
			$context['poll_buttons']['results'] = array('text' => 'poll_results', 'image' => 'poll_results.png', 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start'] . ';viewresults');
794
795 View Code Duplication
		if ($context['allow_change_vote'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
796
			$context['poll_buttons']['change_vote'] = array('text' => 'poll_change_vote', 'image' => 'poll_change_vote.png', 'url' => $scripturl . '?action=vote;topic=' . $context['current_topic'] . '.' . $context['start'] . ';poll=' . $context['poll']['id'] . ';' . $context['session_var'] . '=' . $context['session_id']);
797
798 View Code Duplication
		if ($context['allow_lock_poll'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
799
			$context['poll_buttons']['lock'] = array('text' => (!$context['poll']['is_locked'] ? 'poll_lock' : 'poll_unlock'), 'image' => 'poll_lock.png', 'url' => $scripturl . '?action=lockvoting;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']);
800
801 View Code Duplication
		if ($context['allow_edit_poll'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
802
			$context['poll_buttons']['edit'] = array('text' => 'poll_edit', 'image' => 'poll_edit.png', 'url' => $scripturl . '?action=editpoll;topic=' . $context['current_topic'] . '.' . $context['start']);
803
804
		if ($context['can_remove_poll'])
805
			$context['poll_buttons']['remove_poll'] = array('text' => 'poll_remove', 'image' => 'admin_remove_poll.png', 'custom' => 'data-confirm="' . $txt['poll_remove_warn'] . '"', 'class' => 'you_sure', 'url' => $scripturl . '?action=removepoll;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']);
806
807
		// Allow mods to add additional buttons here
808
		call_integration_hook('integrate_poll_buttons');
809
	}
810
811
	// Calculate the fastest way to get the messages!
812
	$ascending = empty($options['view_newest_first']);
813
	$start = $_REQUEST['start'];
814
	$limit = $context['messages_per_page'];
815
	$firstIndex = 0;
816
	if ($start >= $context['total_visible_posts'] / 2 && $context['messages_per_page'] != -1)
817
	{
818
		$ascending = !$ascending;
819
		$limit = $context['total_visible_posts'] <= $start + $limit ? $context['total_visible_posts'] - $start : $limit;
820
		$start = $context['total_visible_posts'] <= $start + $limit ? 0 : $context['total_visible_posts'] - $start - $limit;
821
		$firstIndex = $limit - 1;
822
	}
823
824
	// Get each post and poster in this topic.
825
	$request = $smcFunc['db_query']('', '
826
		SELECT id_msg, id_member, approved
827
		FROM {db_prefix}messages
828
		WHERE id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || $approve_posts ? '' : '
829
		AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')') . '
830
		ORDER BY id_msg ' . ($ascending ? '' : 'DESC') . ($context['messages_per_page'] == -1 ? '' : '
831
		LIMIT {int:start}, {int:max}'),
832
		array(
833
			'current_member' => $user_info['id'],
834
			'current_topic' => $topic,
835
			'is_approved' => 1,
836
			'blank_id_member' => 0,
837
			'start' => $start,
838
			'max' => $limit,
839
		)
840
	);
841
842
	$messages = array();
843
	$all_posters = array();
844
	while ($row = $smcFunc['db_fetch_assoc']($request))
845
	{
846
		if (!empty($row['id_member']))
847
			$all_posters[$row['id_msg']] = $row['id_member'];
848
		$messages[] = $row['id_msg'];
849
	}
850
	$smcFunc['db_free_result']($request);
851
	$posters = array_unique($all_posters);
852
853
	call_integration_hook('integrate_display_message_list', array(&$messages, &$posters));
854
855
	// Guests can't mark topics read or for notifications, just can't sorry.
856
	if (!$user_info['is_guest'] && !empty($messages))
857
	{
858
		$mark_at_msg = max($messages);
859
		if ($mark_at_msg >= $context['topicinfo']['id_last_msg'])
860
			$mark_at_msg = $modSettings['maxMsgID'];
861
		if ($mark_at_msg >= $context['topicinfo']['new_from'])
862
		{
863
			$smcFunc['db_insert']($context['topicinfo']['new_from'] == 0 ? 'ignore' : 'replace',
864
				'{db_prefix}log_topics',
865
				array(
866
					'id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'unwatched' => 'int',
867
				),
868
				array(
869
					$user_info['id'], $topic, $mark_at_msg, $context['topicinfo']['unwatched'],
870
				),
871
				array('id_member', 'id_topic')
872
			);
873
		}
874
875
		// Check for notifications on this topic OR board.
876
		$request = $smcFunc['db_query']('', '
877
			SELECT sent, id_topic
878
			FROM {db_prefix}log_notify
879
			WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
880
				AND id_member = {int:current_member}
881
			LIMIT 2',
882
			array(
883
				'current_board' => $board,
884
				'current_member' => $user_info['id'],
885
				'current_topic' => $topic,
886
			)
887
		);
888
		$do_once = true;
889
		while ($row = $smcFunc['db_fetch_assoc']($request))
890
		{
891
			// Find if this topic is marked for notification...
892
			if (!empty($row['id_topic']))
893
				$context['is_marked_notify'] = true;
894
895
			// Only do this once, but mark the notifications as "not sent yet" for next time.
896 View Code Duplication
			if (!empty($row['sent']) && $do_once)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
897
			{
898
				$smcFunc['db_query']('', '
899
					UPDATE {db_prefix}log_notify
900
					SET sent = {int:is_not_sent}
901
					WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
902
						AND id_member = {int:current_member}',
903
					array(
904
						'current_board' => $board,
905
						'current_member' => $user_info['id'],
906
						'current_topic' => $topic,
907
						'is_not_sent' => 0,
908
					)
909
				);
910
				$do_once = false;
911
			}
912
		}
913
914
		// Have we recently cached the number of new topics in this board, and it's still a lot?
915
		if (isset($_REQUEST['topicseen']) && isset($_SESSION['topicseen_cache'][$board]) && $_SESSION['topicseen_cache'][$board] > 5)
916
			$_SESSION['topicseen_cache'][$board]--;
917
		// Mark board as seen if this is the only new topic.
918
		elseif (isset($_REQUEST['topicseen']))
919
		{
920
			// Use the mark read tables... and the last visit to figure out if this should be read or not.
921
			$request = $smcFunc['db_query']('', '
922
				SELECT COUNT(*)
923
				FROM {db_prefix}topics AS t
924
					LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = {int:current_board} AND lb.id_member = {int:current_member})
925
					LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
926
				WHERE t.id_board = {int:current_board}
927
					AND t.id_last_msg > COALESCE(lb.id_msg, 0)
928
					AND t.id_last_msg > COALESCE(lt.id_msg, 0)' . (empty($_SESSION['id_msg_last_visit']) ? '' : '
929
					AND t.id_last_msg > {int:id_msg_last_visit}'),
930
				array(
931
					'current_board' => $board,
932
					'current_member' => $user_info['id'],
933
					'id_msg_last_visit' => (int) $_SESSION['id_msg_last_visit'],
934
				)
935
			);
936
			list ($numNewTopics) = $smcFunc['db_fetch_row']($request);
937
			$smcFunc['db_free_result']($request);
938
939
			// If there're no real new topics in this board, mark the board as seen.
940
			if (empty($numNewTopics))
941
				$_REQUEST['boardseen'] = true;
942
			else
943
				$_SESSION['topicseen_cache'][$board] = $numNewTopics;
944
		}
945
		// Probably one less topic - maybe not, but even if we decrease this too fast it will only make us look more often.
946
		elseif (isset($_SESSION['topicseen_cache'][$board]))
947
			$_SESSION['topicseen_cache'][$board]--;
948
949
		// Mark board as seen if we came using last post link from BoardIndex. (or other places...)
950
		if (isset($_REQUEST['boardseen']))
951
		{
952
			$smcFunc['db_insert']('replace',
953
				'{db_prefix}log_boards',
954
				array('id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'),
955
				array($modSettings['maxMsgID'], $user_info['id'], $board),
956
				array('id_member', 'id_board')
957
			);
958
		}
959
	}
960
961
	// Get notification preferences
962
	$context['topicinfo']['notify_prefs'] = array();
963
	if (!empty($user_info['id']))
964
	{
965
		require_once($sourcedir . '/Subs-Notify.php');
966
		$prefs = getNotifyPrefs($user_info['id'], array('topic_notify', 'topic_notify_' . $context['current_topic']), true);
967
		$pref = !empty($prefs[$user_info['id']]) && $context['is_marked_notify'] ? $prefs[$user_info['id']] : array();
968
		$context['topicinfo']['notify_prefs'] = array(
969
			'is_custom' => isset($pref['topic_notify_' . $topic]),
970
			'pref' => isset($pref['topic_notify_' . $context['current_topic']]) ? $pref['topic_notify_' . $context['current_topic']] : (!empty($pref['topic_notify']) ? $pref['topic_notify'] : 0),
971
		);
972
	}
973
974
	$context['topic_notification'] = !empty($user_info['id']) ? $context['topicinfo']['notify_prefs'] : array();
975
	// 0 => unwatched, 1 => normal, 2 => receive alerts, 3 => receive emails
976
	$context['topic_notification_mode'] = !$user_info['is_guest'] ? ($context['topic_unwatched'] ? 0 : ($context['topicinfo']['notify_prefs']['pref'] & 0x02 ? 3 : ($context['topicinfo']['notify_prefs']['pref'] & 0x01 ? 2 : 1))) : 0;
977
978
	$context['loaded_attachments'] = array();
979
980
	// If there _are_ messages here... (probably an error otherwise :!)
981
	if (!empty($messages))
982
	{
983
		// Fetch attachments.
984
		if (!empty($modSettings['attachmentEnable']) && allowedTo('view_attachments'))
985
		{
986
			$request = $smcFunc['db_query']('', '
987
				SELECT
988
					a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, COALESCE(a.size, 0) AS filesize, a.downloads, a.approved,
989
					a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
990
					COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
991
				FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
992
					LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
993
				WHERE a.id_msg IN ({array_int:message_list})
994
					AND a.attachment_type = {int:attachment_type}',
995
				array(
996
					'message_list' => $messages,
997
					'attachment_type' => 0,
998
					'is_approved' => 1,
999
				)
1000
			);
1001
			$temp = array();
1002
			while ($row = $smcFunc['db_fetch_assoc']($request))
1003
			{
1004 View Code Duplication
				if (!$row['approved'] && $modSettings['postmod_active'] && !allowedTo('approve_posts') && (!isset($all_posters[$row['id_msg']]) || $all_posters[$row['id_msg']] != $user_info['id']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1005
					continue;
1006
1007
				$temp[$row['id_attach']] = $row;
1008
				$temp[$row['id_attach']]['topic'] = $topic;
1009
				$temp[$row['id_attach']]['board'] = $board;
1010
1011
				if (!isset($context['loaded_attachments'][$row['id_msg']]))
1012
					$context['loaded_attachments'][$row['id_msg']] = array();
1013
			}
1014
			$smcFunc['db_free_result']($request);
1015
1016
			// This is better than sorting it with the query...
1017
			ksort($temp);
1018
1019
			foreach ($temp as $row)
1020
				$context['loaded_attachments'][$row['id_msg']][] = $row;
1021
		}
1022
1023
		$msg_parameters = array(
1024
			'message_list' => $messages,
1025
			'new_from' => $context['topicinfo']['new_from'],
1026
		);
1027
		$msg_selects = array();
1028
		$msg_tables = array();
1029
		call_integration_hook('integrate_query_message', array(&$msg_selects, &$msg_tables, &$msg_parameters));
1030
1031
		// What?  It's not like it *couldn't* be only guests in this topic...
1032
		loadMemberData($posters);
1033
		$messages_request = $smcFunc['db_query']('', '
1034
			SELECT
1035
				id_msg, icon, subject, poster_time, poster_ip, id_member, modified_time, modified_name, modified_reason, body,
1036
				smileys_enabled, poster_name, poster_email, approved, likes,
1037
				id_msg_modified < {int:new_from} AS is_read
1038
				' . (!empty($msg_selects) ? (', '. implode(', ', $msg_selects)) : '') . '
1039
			FROM {db_prefix}messages
1040
				' . (!empty($msg_tables) ? implode("\n\t", $msg_tables) : '') . '
1041
			WHERE id_msg IN ({array_int:message_list})
1042
			ORDER BY id_msg' . (empty($options['view_newest_first']) ? '' : ' DESC'),
1043
			$msg_parameters
1044
		);
1045
1046
		// And the likes
1047
		if (!empty($modSettings['enable_likes']))
1048
			$context['my_likes'] = $context['user']['is_guest'] ? array() : prepareLikesContext($topic);
1049
1050
		// Go to the last message if the given time is beyond the time of the last message.
1051 View Code Duplication
		if (isset($context['start_from']) && $context['start_from'] >= $context['topicinfo']['num_replies'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1052
			$context['start_from'] = $context['topicinfo']['num_replies'];
1053
1054
		// Since the anchor information is needed on the top of the page we load these variables beforehand.
1055
		$context['first_message'] = isset($messages[$firstIndex]) ? $messages[$firstIndex] : $messages[0];
1056
		if (empty($options['view_newest_first']))
1057
			$context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $context['start_from'];
1058 View Code Duplication
		else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1059
			$context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $context['topicinfo']['num_replies'] - $context['start_from'];
1060
	}
1061
	else
1062
	{
1063
		$messages_request = false;
1064
		$context['first_message'] = 0;
1065
		$context['first_new_message'] = false;
1066
1067
		$context['likes'] = array();
1068
	}
1069
1070
	$context['jump_to'] = array(
1071
		'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
1072
		'board_name' => $smcFunc['htmlspecialchars'](strtr(strip_tags($board_info['name']), array('&amp;' => '&'))),
1073
		'child_level' => $board_info['child_level'],
1074
	);
1075
1076
	// Set the callback.  (do you REALIZE how much memory all the messages would take?!?)
1077
	// This will be called from the template.
1078
	$context['get_message'] = 'prepareDisplayContext';
1079
1080
	// Now set all the wonderful, wonderful permissions... like moderation ones...
1081
	$common_permissions = array(
1082
		'can_approve' => 'approve_posts',
1083
		'can_ban' => 'manage_bans',
1084
		'can_sticky' => 'make_sticky',
1085
		'can_merge' => 'merge_any',
1086
		'can_split' => 'split_any',
1087
		'calendar_post' => 'calendar_post',
1088
		'can_send_pm' => 'pm_send',
1089
		'can_report_moderator' => 'report_any',
1090
		'can_moderate_forum' => 'moderate_forum',
1091
		'can_issue_warning' => 'issue_warning',
1092
		'can_restore_topic' => 'move_any',
1093
		'can_restore_msg' => 'move_any',
1094
		'can_see_likes' => 'likes_view',
1095
		'can_like' => 'likes_like',
1096
	);
1097
	foreach ($common_permissions as $contextual => $perm)
1098
		$context[$contextual] = allowedTo($perm);
1099
1100
	// Permissions with _any/_own versions.  $context[YYY] => ZZZ_any/_own.
1101
	$anyown_permissions = array(
1102
		'can_move' => 'move',
1103
		'can_lock' => 'lock',
1104
		'can_delete' => 'remove',
1105
		'can_add_poll' => 'poll_add',
1106
		'can_remove_poll' => 'poll_remove',
1107
		'can_reply' => 'post_reply',
1108
		'can_reply_unapproved' => 'post_unapproved_replies',
1109
		'can_view_warning' => 'profile_warning',
1110
	);
1111
	foreach ($anyown_permissions as $contextual => $perm)
1112
		$context[$contextual] = allowedTo($perm . '_any') || ($context['user']['started'] && allowedTo($perm . '_own'));
1113
1114
	if (!$user_info['is_admin'] && !$modSettings['topic_move_any'])
1115
	{
1116
		// We'll use this in a minute
1117
		$boards_allowed = array_diff(boardsAllowedTo('post_new'), array($board));
1118
1119
		/* You can't move this unless you have permission
1120
			to start new topics on at least one other board */
1121
		$context['can_move'] &= count($boards_allowed) > 1;
1122
	}
1123
1124
	// If a topic is locked, you can't remove it unless it's yours and you locked it or you can lock_any
1125
	if ($context['topicinfo']['locked'])
1126
	{
1127
		$context['can_delete'] &= (($context['topicinfo']['locked'] == 1 && $context['user']['started']) || allowedTo('lock_any'));
1128
	}
1129
1130
	// Cleanup all the permissions with extra stuff...
1131
	$context['can_mark_notify'] = !$context['user']['is_guest'];
1132
	$context['calendar_post'] &= !empty($modSettings['cal_enabled']);
1133
	$context['can_add_poll'] &= $modSettings['pollMode'] == '1' && $context['topicinfo']['id_poll'] <= 0;
1134
	$context['can_remove_poll'] &= $modSettings['pollMode'] == '1' && $context['topicinfo']['id_poll'] > 0;
1135
	$context['can_reply'] &= empty($context['topicinfo']['locked']) || allowedTo('moderate_board');
1136
	$context['can_reply_unapproved'] &= $modSettings['postmod_active'] && (empty($context['topicinfo']['locked']) || allowedTo('moderate_board'));
1137
	$context['can_issue_warning'] &= $modSettings['warning_settings'][0] == 1;
1138
	// Handle approval flags...
1139
	$context['can_reply_approved'] = $context['can_reply'];
1140
	$context['can_reply'] |= $context['can_reply_unapproved'];
1141
	$context['can_quote'] = $context['can_reply'] && (empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])));
1142
	$context['can_mark_unread'] = !$user_info['is_guest'];
1143
	$context['can_unwatch'] = !$user_info['is_guest'];
1144
	$context['can_set_notify'] = !$user_info['is_guest'];
1145
1146
	$context['can_print'] = empty($modSettings['disable_print_topic']);
1147
1148
	// Start this off for quick moderation - it will be or'd for each post.
1149
	$context['can_remove_post'] = allowedTo('delete_any') || (allowedTo('delete_replies') && $context['user']['started']);
1150
1151
	// Can restore topic?  That's if the topic is in the recycle board and has a previous restore state.
1152
	$context['can_restore_topic'] &= !empty($board_info['recycle']) && !empty($context['topicinfo']['id_previous_board']);
1153
	$context['can_restore_msg'] &= !empty($board_info['recycle']) && !empty($context['topicinfo']['id_previous_topic']);
1154
1155
	// Check if the draft functions are enabled and that they have permission to use them (for quick reply.)
1156
	$context['drafts_save'] = !empty($modSettings['drafts_post_enabled']) && allowedTo('post_draft') && $context['can_reply'];
1157
	$context['drafts_autosave'] = !empty($context['drafts_save']) && !empty($modSettings['drafts_autosave_enabled']);
1158
	if (!empty($context['drafts_save']))
1159
		loadLanguage('Drafts');
1160
1161
	// When was the last time this topic was replied to?  Should we warn them about it?
1162
	if (!empty($modSettings['oldTopicDays']) && ($context['can_reply'] || $context['can_reply_unapproved']) && empty($context['topicinfo']['is_sticky']))
1163
	{
1164
		$request = $smcFunc['db_query']('', '
1165
			SELECT poster_time
1166
			FROM {db_prefix}messages
1167
			WHERE id_msg = {int:id_last_msg}
1168
			LIMIT 1',
1169
			array(
1170
				'id_last_msg' => $context['topicinfo']['id_last_msg'],
1171
			)
1172
		);
1173
1174
		list ($lastPostTime) = $smcFunc['db_fetch_row']($request);
1175
		$smcFunc['db_free_result']($request);
1176
1177
		$context['oldTopicError'] = $lastPostTime + $modSettings['oldTopicDays'] * 86400 < time();
1178
	}
1179
1180
	// You can't link an existing topic to the calendar unless you can modify the first post...
1181
	$context['calendar_post'] &= allowedTo('modify_any') || (allowedTo('modify_own') && $context['user']['started']);
1182
1183
	// Load up the "double post" sequencing magic.
1184
	checkSubmitOnce('register');
1185
	$context['name'] = isset($_SESSION['guest_name']) ? $_SESSION['guest_name'] : '';
1186
	$context['email'] = isset($_SESSION['guest_email']) ? $_SESSION['guest_email'] : '';
1187
	// Needed for the editor and message icons.
1188
	require_once($sourcedir . '/Subs-Editor.php');
1189
1190
	// Now create the editor.
1191
	$editorOptions = array(
1192
		'id' => 'quickReply',
1193
		'value' => '',
1194
		'disable_smiley_box' => empty($options['use_editor_quick_reply']),
1195
		'labels' => array(
1196
			'post_button' => $txt['post'],
1197
		),
1198
		// add height and width for the editor
1199
		'height' => '250px',
1200
		'width' => '100%',
1201
		// We do HTML preview here.
1202
		'preview_type' => 1,
1203
		// This is required
1204
		'required' => true,
1205
	);
1206
	create_control_richedit($editorOptions);
1207
1208
	// Store the ID.
1209
	$context['post_box_name'] = $editorOptions['id'];
1210
1211
	// Set a flag so the sub template knows what to do...
1212
	$context['show_bbc'] = !empty($options['use_editor_quick_reply']);
1213
	$modSettings['disable_wysiwyg'] = !empty($options['use_editor_quick_reply']);
1214
	$context['attached'] = '';
1215
	$context['make_poll'] = isset($_REQUEST['poll']);
1216
1217
	// Message icons - customized icons are off?
1218
	$context['icons'] = getMessageIcons($board);
1219
1220 View Code Duplication
	if (!empty($context['icons']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1221
		$context['icons'][count($context['icons']) - 1]['is_last'] = true;
1222
1223
	// Build the normal button array.
1224
	$context['normal_buttons'] = array();
1225
1226
	if ($context['can_reply'])
1227
		$context['normal_buttons']['reply'] = array('text' => 'reply', 'image' => 'reply.png', 'url' => $scripturl . '?action=post;topic=' . $context['current_topic'] . '.' . $context['start'] . ';last_msg=' . $context['topic_last_message'], 'active' => true);
1228
1229 View Code Duplication
	if ($context['can_add_poll'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1230
		$context['normal_buttons']['add_poll'] = array('text' => 'add_poll', 'image' => 'add_poll.png', 'url' => $scripturl . '?action=editpoll;add;topic=' . $context['current_topic'] . '.' . $context['start']);
1231
1232 View Code Duplication
	if ($context['can_mark_unread'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1233
		$context['normal_buttons']['mark_unread'] = array('text' => 'mark_unread', 'image' => 'markunread.png', 'url' => $scripturl . '?action=markasread;sa=topic;t=' . $context['mark_unread_time'] . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']);
1234
1235
	if ($context['can_print'])
1236
		$context['normal_buttons']['print'] = array('text' => 'print', 'image' => 'print.png', 'custom' => 'rel="nofollow"', 'url' => $scripturl . '?action=printpage;topic=' . $context['current_topic'] . '.0');
1237
	
1238
	if ($context['can_set_notify'])
1239
		$context['normal_buttons']['notify'] = array(
1240
			'text' => 'notify_topic_' . $context['topic_notification_mode'],
1241
			'sub_buttons' => array(
1242
				array(
1243
					'test' => 'can_unwatch',
1244
					'text' => 'notify_topic_0',
1245
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=0;' . $context['session_var'] . '=' . $context['session_id'],
1246
				),
1247
				array(
1248
					'text' => 'notify_topic_1',
1249
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=1;' . $context['session_var'] . '=' . $context['session_id'],
1250
				),
1251
				array(
1252
					'text' => 'notify_topic_2',
1253
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=2;' . $context['session_var'] . '=' . $context['session_id'],
1254
				),
1255
				array(
1256
					'text' => 'notify_topic_3',
1257
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=3;' . $context['session_var'] . '=' . $context['session_id'],
1258
				),
1259
			),
1260
		);
1261
1262
	// Build the mod button array
1263
	$context['mod_buttons'] = array();
1264
1265 View Code Duplication
	if ($context['can_move'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1266
		$context['mod_buttons']['move'] = array('text' => 'move_topic', 'image' => 'admin_move.png', 'url' => $scripturl . '?action=movetopic;current_board=' . $context['current_board'] . ';topic=' . $context['current_topic'] . '.0');
1267
1268 View Code Duplication
	if ($context['can_delete'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1269
		$context['mod_buttons']['delete'] = array('text' => 'remove_topic', 'image' => 'admin_rem.png', 'custom' => 'data-confirm="' . $txt['are_sure_remove_topic'] . '"', 'class' => 'you_sure', 'url' => $scripturl . '?action=removetopic2;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id']);
1270
1271 View Code Duplication
	if ($context['can_lock'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1272
		$context['mod_buttons']['lock'] = array('text' => empty($context['is_locked']) ? 'set_lock' : 'set_unlock', 'image' => 'admin_lock.png', 'url' => $scripturl . '?action=lock;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']);
1273
1274 View Code Duplication
	if ($context['can_sticky'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1275
		$context['mod_buttons']['sticky'] = array('text' => empty($context['is_sticky']) ? 'set_sticky' : 'set_nonsticky', 'image' => 'admin_sticky.png', 'url' => $scripturl . '?action=sticky;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']);
1276
1277 View Code Duplication
	if ($context['can_merge'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1278
		$context['mod_buttons']['merge'] = array('text' => 'merge', 'image' => 'merge.png', 'url' => $scripturl . '?action=mergetopics;board=' . $context['current_board'] . '.0;from=' . $context['current_topic']);
1279
1280 View Code Duplication
	if ($context['calendar_post'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1281
		$context['mod_buttons']['calendar'] = array('text' => 'calendar_link', 'image' => 'linktocal.png', 'url' => $scripturl . '?action=post;calendar;msg=' . $context['topic_first_message'] . ';topic=' . $context['current_topic'] . '.0');
1282
1283
	// Restore topic. eh?  No monkey business.
1284 View Code Duplication
	if ($context['can_restore_topic'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1285
		$context['mod_buttons']['restore_topic'] = array('text' => 'restore_topic', 'image' => '', 'url' => $scripturl . '?action=restoretopic;topics=' . $context['current_topic'] . ';' . $context['session_var'] . '=' . $context['session_id']);
1286
1287
	// Show a message in case a recently posted message became unapproved.
1288
	$context['becomesUnapproved'] = !empty($_SESSION['becomesUnapproved']) ? true : false;
1289
1290
	// Don't want to show this forever...
1291
	if ($context['becomesUnapproved'])
1292
		unset($_SESSION['becomesUnapproved']);
1293
1294
	// Allow adding new mod buttons easily.
1295
	// Note: $context['normal_buttons'] and $context['mod_buttons'] are added for backward compatibility with 2.0, but are deprecated and should not be used
1296
	call_integration_hook('integrate_display_buttons', array(&$context['normal_buttons']));
1297
	// Note: integrate_mod_buttons is no more necessary and deprecated, but is kept for backward compatibility with 2.0
1298
	call_integration_hook('integrate_mod_buttons', array(&$context['mod_buttons']));
1299
1300
	// Load the drafts js file
1301
	if ($context['drafts_autosave'])
1302
		loadJavascriptFile('drafts.js', array('defer' => false), 'smf_drafts');
1303
1304
	// Spellcheck
1305
	if ($context['show_spellchecking'])
1306
		loadJavascriptFile('spellcheck.js', array('defer' => false), 'smf_spellcheck');
1307
1308
	// topic.js
1309
	loadJavascriptFile('topic.js', array('defer' => false), 'smf_topic');
1310
1311
	// quotedText.js
1312
	loadJavascriptFile('quotedText.js', array('defer' => true), 'smf_quotedText');
1313
1314
	// Mentions
1315 View Code Duplication
	if (!empty($modSettings['enable_mentions']) && allowedTo('mention'))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1316
	{
1317
		loadJavascriptFile('jquery.atwho.min.js', array('defer' => true), 'smf_atwho');
1318
		loadJavascriptFile('jquery.caret.min.js', array('defer' => true), 'smf_caret');
1319
		loadJavascriptFile('mentions.js', array('defer' => true), 'smf_mentions');
1320
	}
1321
}
1322
1323
/**
1324
 * Callback for the message display.
1325
 * It actually gets and prepares the message context.
1326
 * This function will start over from the beginning if reset is set to true, which is
1327
 * useful for showing an index before or after the posts.
1328
 *
1329
 * @param bool $reset Whether or not to reset the db seek pointer
1330
 * @return array A large array of contextual data for the posts
1331
 */
1332
function prepareDisplayContext($reset = false)
1333
{
1334
	global $settings, $txt, $modSettings, $scripturl, $options, $user_info, $smcFunc;
1335
	global $memberContext, $context, $messages_request, $topic, $board_info, $sourcedir;
1336
1337
	static $counter = null;
1338
1339
	// If the query returned false, bail.
1340
	if ($messages_request == false)
1341
		return false;
1342
1343
	// Remember which message this is.  (ie. reply #83)
1344
	if ($counter === null || $reset)
1345
		$counter = empty($options['view_newest_first']) ? $context['start'] : $context['total_visible_posts'] - $context['start'];
1346
1347
	// Start from the beginning...
1348
	if ($reset)
1349
		return @$smcFunc['db_data_seek']($messages_request, 0);
1350
1351
	// Attempt to get the next message.
1352
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1353
	if (!$message)
1354
	{
1355
		$smcFunc['db_free_result']($messages_request);
1356
		return false;
1357
	}
1358
1359
	// $context['icon_sources'] says where each icon should come from - here we set up the ones which will always exist!
1360
	if (empty($context['icon_sources']))
1361
	{
1362
		$context['icon_sources'] = array();
1363
		foreach ($context['stable_icons'] as $icon)
1364
			$context['icon_sources'][$icon] = 'images_url';
1365
	}
1366
1367
	// Message Icon Management... check the images exist.
1368
	if (empty($modSettings['messageIconChecks_disable']))
1369
	{
1370
		// If the current icon isn't known, then we need to do something...
1371
		if (!isset($context['icon_sources'][$message['icon']]))
1372
			$context['icon_sources'][$message['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['icon'] . '.png') ? 'images_url' : 'default_images_url';
1373
	}
1374 View Code Duplication
	elseif (!isset($context['icon_sources'][$message['icon']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1375
		$context['icon_sources'][$message['icon']] = 'images_url';
1376
1377
	// If you're a lazy bum, you probably didn't give a subject...
1378
	$message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject'];
1379
1380
	// Are you allowed to remove at least a single reply?
1381
	$context['can_remove_post'] |= allowedTo('delete_own') && (empty($modSettings['edit_disable_time']) || $message['poster_time'] + $modSettings['edit_disable_time'] * 60 >= time()) && $message['id_member'] == $user_info['id'];
1382
1383
	// If the topic is locked, you might not be able to delete the post...
1384
	if ($context['is_locked'])
1385
	{
1386
		$context['can_remove_post'] &= ($context['user']['started'] && $context['is_locked'] == 1) || allowedTo('lock_any');
1387
	}
1388
1389
	// If it couldn't load, or the user was a guest.... someday may be done with a guest table.
1390
	if (!loadMemberContext($message['id_member'], true))
1391
	{
1392
		// Notice this information isn't used anywhere else....
1393
		$memberContext[$message['id_member']]['name'] = $message['poster_name'];
1394
		$memberContext[$message['id_member']]['id'] = 0;
1395
		$memberContext[$message['id_member']]['group'] = $txt['guest_title'];
1396
		$memberContext[$message['id_member']]['link'] = $message['poster_name'];
1397
		$memberContext[$message['id_member']]['email'] = $message['poster_email'];
1398
		$memberContext[$message['id_member']]['show_email'] = allowedTo('moderate_forum');
1399
		$memberContext[$message['id_member']]['is_guest'] = true;
1400
	}
1401
	else
1402
	{
1403
		// Define this here to make things a bit more readable
1404
		$can_view_warning = $context['user']['can_mod'] || allowedTo('view_warning_any') || ($message['id_member'] == $user_info['id'] && allowedTo('view_warning_own'));
1405
1406
		$memberContext[$message['id_member']]['can_view_profile'] = allowedTo('profile_view') || ($message['id_member'] == $user_info['id'] && !$user_info['is_guest']);
1407
		$memberContext[$message['id_member']]['is_topic_starter'] = $message['id_member'] == $context['topic_starter_id'];
1408
		$memberContext[$message['id_member']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member']]['warning_status'] && $can_view_warning;
1409
		// Show the email if it's your post...
1410
		$memberContext[$message['id_member']]['show_email'] |= ($message['id_member'] == $user_info['id']);
1411
	}
1412
1413
	$memberContext[$message['id_member']]['ip'] = inet_dtop($message['poster_ip']);
1414
	$memberContext[$message['id_member']]['show_profile_buttons'] = !empty($modSettings['show_profile_buttons']) && (!empty($memberContext[$message['id_member']]['can_view_profile']) || (!empty($memberContext[$message['id_member']]['website']['url']) && !isset($context['disabled_fields']['website'])) || $memberContext[$message['id_member']]['show_email'] || $context['can_send_pm']);
1415
1416
	// Do the censor thang.
1417
	censorText($message['body']);
1418
	censorText($message['subject']);
1419
1420
	// Run BBC interpreter on the message.
1421
	$message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1422
1423
	// If it's in the recycle bin we need to override whatever icon we did have.
1424
	if (!empty($board_info['recycle']))
1425
		$message['icon'] = 'recycled';
1426
1427
	require_once($sourcedir . '/Subs-Attachments.php');
1428
1429
	// Compose the memory eat- I mean message array.
1430
	$output = array(
1431
		'attachment' => loadAttachmentContext($message['id_msg'], $context['loaded_attachments']),
1432
		'id' => $message['id_msg'],
1433
		'href' => $scripturl . '?topic=' . $topic . '.msg' . $message['id_msg'] . '#msg' . $message['id_msg'],
1434
		'link' => '<a href="' . $scripturl . '?msg=' . $message['id_msg'] . '" rel="nofollow">' . $message['subject'] . '</a>',
1435
		'member' => &$memberContext[$message['id_member']],
1436
		'icon' => $message['icon'],
1437
		'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.png',
1438
		'subject' => $message['subject'],
1439
		'time' => timeformat($message['poster_time']),
1440
		'timestamp' => forum_time(true, $message['poster_time']),
1441
		'counter' => $counter,
1442
		'modified' => array(
1443
			'time' => timeformat($message['modified_time']),
1444
			'timestamp' => forum_time(true, $message['modified_time']),
1445
			'name' => $message['modified_name'],
1446
			'reason' => $message['modified_reason']
1447
		),
1448
		'body' => $message['body'],
1449
		'new' => empty($message['is_read']),
1450
		'approved' => $message['approved'],
1451
		'first_new' => isset($context['start_from']) && $context['start_from'] == $counter,
1452
		'is_ignored' => !empty($modSettings['enable_buddylist']) && !empty($options['posts_apply_ignore_list']) && in_array($message['id_member'], $context['user']['ignoreusers']),
1453
		'can_approve' => !$message['approved'] && $context['can_approve'],
1454
		'can_unapprove' => !empty($modSettings['postmod_active']) && $context['can_approve'] && $message['approved'],
1455
		'can_modify' => (!$context['is_locked'] || allowedTo('moderate_board')) && (allowedTo('modify_any') || (allowedTo('modify_replies') && $context['user']['started']) || (allowedTo('modify_own') && $message['id_member'] == $user_info['id'] && (empty($modSettings['edit_disable_time']) || !$message['approved'] || $message['poster_time'] + $modSettings['edit_disable_time'] * 60 > time()))),
1456
		'can_remove' => allowedTo('delete_any') || (allowedTo('delete_replies') && $context['user']['started']) || (allowedTo('delete_own') && $message['id_member'] == $user_info['id'] && (empty($modSettings['edit_disable_time']) || $message['poster_time'] + $modSettings['edit_disable_time'] * 60 > time())),
1457
		'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member'] == $user_info['id'] && !empty($user_info['id'])),
1458
		'css_class' => $message['approved'] ? 'windowbg' : 'approvebg',
1459
	);
1460
1461
	// Does the file contains any attachments? if so, change the icon.
1462
	if (!empty($output['attachment']))
1463
	{
1464
		$output['icon'] = 'clip';
1465
		$output['icon_url'] = $settings[$context['icon_sources'][$output['icon']]] . '/post/' . $output['icon'] . '.png';
1466
	}
1467
1468
	// Are likes enable?
1469 View Code Duplication
	if (!empty($modSettings['enable_likes']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1470
		$output['likes'] = array(
1471
			'count' => $message['likes'],
1472
			'you' => in_array($message['id_msg'], $context['my_likes']),
1473
			'can_like' => !$context['user']['is_guest'] && $message['id_member'] != $context['user']['id'] && !empty($context['can_like']),
1474
		);
1475
1476
	// Is this user the message author?
1477
	$output['is_message_author'] = $message['id_member'] == $user_info['id'];
1478 View Code Duplication
	if (!empty($output['modified']['name']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1479
		$output['modified']['last_edit_text'] = sprintf($txt['last_edit_by'], $output['modified']['time'], $output['modified']['name']);
1480
1481
	// Did they give a reason for editing?
1482 View Code Duplication
	if (!empty($output['modified']['name']) && !empty($output['modified']['reason']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1483
		$output['modified']['last_edit_text'] .= '&nbsp;' . sprintf($txt['last_edit_reason'], $output['modified']['reason']);
1484
1485
	// Any custom profile fields?
1486
	if (!empty($memberContext[$message['id_member']]['custom_fields']))
1487
		foreach ($memberContext[$message['id_member']]['custom_fields'] as $custom)
1488
			$output['custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
1489
1490
	if (empty($options['view_newest_first']))
1491
		$counter++;
1492
1493
	else
1494
		$counter--;
1495
1496
	call_integration_hook('integrate_prepare_display_context', array(&$output, &$message, $counter));
1497
1498
	return $output;
1499
}
1500
1501
/**
1502
 * Downloads an attachment, and increments the download count.
1503
 * It requires the view_attachments permission.
1504
 * It disables the session parser, and clears any previous output.
1505
 * It depends on the attachmentUploadDir setting being correct.
1506
 * It is accessed via the query string ?action=dlattach.
1507
 * Views to attachments do not increase hits and are not logged in the "Who's Online" log.
1508
 * Legacy code, all attachments are now handled by ShowAttachments.php
1509
 */
1510
function Download()
1511
{
1512
	global $txt, $modSettings, $user_info, $context, $topic, $smcFunc;
1513
1514
	// Some defaults that we need.
1515
	$context['character_set'] = empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'ISO-8859-1' : $txt['lang_character_set']) : $modSettings['global_character_set'];
1516
	$context['utf8'] = $context['character_set'] === 'UTF-8';
1517
	$context['no_last_modified'] = true;
1518
1519
	// Prevent a preview image from being displayed twice.
1520
	if (isset($_GET['action']) && $_GET['action'] == 'dlattach' && isset($_GET['type']) && ($_GET['type'] == 'avatar' || $_GET['type'] == 'preview'))
1521
		return;
1522
1523
	// Make sure some attachment was requested!
1524
	if (!isset($_REQUEST['attach']) && !isset($_REQUEST['id']))
1525
		fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1526
1527
	$_REQUEST['attach'] = isset($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : (int) $_REQUEST['id'];
1528
1529
	// Do we have a hook wanting to use our attachment system? We use $attachRequest to prevent accidental usage of $request.
1530
	$attachRequest = null;
1531
	call_integration_hook('integrate_download_request', array(&$attachRequest));
1532
	if (!is_null($attachRequest) && $smcFunc['db_is_resource']($attachRequest))
1533
		$request = $attachRequest;
1534
	else
1535
	{
1536
		// This checks only the current board for $board/$topic's permissions.
1537
		isAllowedTo('view_attachments');
1538
1539
		// Make sure this attachment is on this board.
1540
		// @todo: We must verify that $topic is the attachment's topic, or else the permission check above is broken.
1541
		$request = $smcFunc['db_query']('', '
1542
			SELECT a.id_folder, a.filename, a.file_hash, a.fileext, a.id_attach, a.attachment_type, a.mime_type, a.approved, m.id_member
1543
			FROM {db_prefix}attachments AS a
1544
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
1545
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1546
			WHERE a.id_attach = {int:attach}
1547
			LIMIT 1',
1548
			array(
1549
				'attach' => $_REQUEST['attach'],
1550
				'current_topic' => $topic,
1551
			)
1552
		);
1553
	}
1554
1555
	if ($smcFunc['db_num_rows']($request) == 0)
1556
		fatal_lang_error('no_access', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1557
1558
	list ($id_folder, $real_filename, $file_hash, $file_ext, $id_attach, $attachment_type, $mime_type, $is_approved, $id_member) = $smcFunc['db_fetch_row']($request);
1559
	$smcFunc['db_free_result']($request);
1560
1561
	// If it isn't yet approved, do they have permission to view it?
1562
	if (!$is_approved && ($id_member == 0 || $user_info['id'] != $id_member) && ($attachment_type == 0 || $attachment_type == 3))
1563
		isAllowedTo('approve_posts');
1564
1565
	// Update the download counter (unless it's a thumbnail).
1566
	if ($attachment_type != 3)
1567
		$smcFunc['db_query']('attach_download_increase', '
1568
			UPDATE LOW_PRIORITY {db_prefix}attachments
1569
			SET downloads = downloads + 1
1570
			WHERE id_attach = {int:id_attach}',
1571
			array(
1572
				'id_attach' => $id_attach,
1573
			)
1574
		);
1575
1576
	$filename = getAttachmentFilename($real_filename, $_REQUEST['attach'], $id_folder, false, $file_hash);
1577
1578
	// This is done to clear any output that was made before now.
1579
	ob_end_clean();
1580
	if (!empty($modSettings['enableCompressedOutput']) && @filesize($filename) <= 4194304 && in_array($file_ext, array('txt', 'html', 'htm', 'js', 'doc', 'docx', 'rtf', 'css', 'php', 'log', 'xml', 'sql', 'c', 'java')))
1581
		@ob_start('ob_gzhandler');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1582
1583
	else
1584
	{
1585
		ob_start();
1586
		header('Content-Encoding: none');
1587
	}
1588
1589
	// No point in a nicer message, because this is supposed to be an attachment anyway...
1590 View Code Duplication
	if (!file_exists($filename))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1591
	{
1592
		header((preg_match('~HTTP/1\.[01]~i', $_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0') . ' 404 Not Found');
1593
		header('Content-Type: text/plain; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
1594
1595
		// We need to die like this *before* we send any anti-caching headers as below.
1596
		die('File not found.');
1597
	}
1598
1599
	// If it hasn't been modified since the last time this attachment was retrieved, there's no need to display it again.
1600 View Code Duplication
	if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1601
	{
1602
		list($modified_since) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
1603
		if (strtotime($modified_since) >= filemtime($filename))
1604
		{
1605
			ob_end_clean();
1606
1607
			// Answer the question - no, it hasn't been modified ;).
1608
			header('HTTP/1.1 304 Not Modified');
1609
			exit;
1610
		}
1611
	}
1612
1613
	// Check whether the ETag was sent back, and cache based on that...
1614
	$eTag = '"' . substr($_REQUEST['attach'] . $real_filename . filemtime($filename), 0, 64) . '"';
1615 View Code Duplication
	if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $eTag) !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1616
	{
1617
		ob_end_clean();
1618
1619
		header('HTTP/1.1 304 Not Modified');
1620
		exit;
1621
	}
1622
1623
	// Send the attachment headers.
1624
	header('Pragma: ');
1625
1626
	if (!isBrowser('gecko'))
1627
		header('Content-Transfer-Encoding: binary');
1628
1629
	header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT');
1630
	header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filename)) . ' GMT');
1631
	header('Accept-Ranges: bytes');
1632
	header('Connection: close');
1633
	header('ETag: ' . $eTag);
1634
1635
	// Make sure the mime type warrants an inline display.
1636
	if (isset($_REQUEST['image']) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0)
1637
		unset($_REQUEST['image']);
1638
1639
	// Does this have a mime type?
1640
	elseif (!empty($mime_type) && (isset($_REQUEST['image']) || !in_array($file_ext, array('jpg', 'gif', 'jpeg', 'x-ms-bmp', 'png', 'psd', 'tiff', 'iff'))))
1641
		header('Content-Type: ' . strtr($mime_type, array('image/bmp' => 'image/x-ms-bmp')));
1642
1643 View Code Duplication
	else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1644
	{
1645
		header('Content-Type: ' . (isBrowser('ie') || isBrowser('opera') ? 'application/octetstream' : 'application/octet-stream'));
1646
		if (isset($_REQUEST['image']))
1647
			unset($_REQUEST['image']);
1648
	}
1649
1650
	// Convert the file to UTF-8, cuz most browsers dig that.
1651
	$utf8name = !$context['utf8'] && function_exists('iconv') ? iconv($context['character_set'], 'UTF-8', $real_filename) : (!$context['utf8'] && function_exists('mb_convert_encoding') ? mb_convert_encoding($real_filename, 'UTF-8', $context['character_set']) : $real_filename);
1652
	$disposition = !isset($_REQUEST['image']) ? 'attachment' : 'inline';
1653
1654
	// Different browsers like different standards...
1655 View Code Duplication
	if (isBrowser('firefox'))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1656
		header('Content-Disposition: ' . $disposition . '; filename*=UTF-8\'\'' . rawurlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name)));
1657
1658
	elseif (isBrowser('opera'))
1659
		header('Content-Disposition: ' . $disposition . '; filename="' . preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name) . '"');
1660
1661
	elseif (isBrowser('ie'))
1662
		header('Content-Disposition: ' . $disposition . '; filename="' . urlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name)) . '"');
1663
1664
	else
1665
		header('Content-Disposition: ' . $disposition . '; filename="' . $utf8name . '"');
1666
1667
	// If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE.
1668 View Code Duplication
	if (!isset($_REQUEST['image']) && in_array($file_ext, array('gif', 'jpg', 'bmp', 'png', 'jpeg', 'tiff')))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1669
		header('Cache-Control: no-cache');
1670
	else
1671
		header('Cache-Control: max-age=' . (525600 * 60) . ', private');
1672
1673
	header('Content-Length: ' . filesize($filename));
1674
1675
	// Try to buy some time...
1676
	@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1677
1678
	// Recode line endings for text files, if enabled.
1679 View Code Duplication
	if (!empty($modSettings['attachmentRecodeLineEndings']) && !isset($_REQUEST['image']) && in_array($file_ext, array('txt', 'css', 'htm', 'html', 'php', 'xml')))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1680
	{
1681
		if (strpos($_SERVER['HTTP_USER_AGENT'], 'Windows') !== false)
1682
			$callback = function ($buffer)
1683
			{
1684
				return preg_replace('~[\r]?\n~', "\r\n", $buffer);
1685
			};
1686
		elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') !== false)
1687
			$callback = function ($buffer)
1688
			{
1689
				return preg_replace('~[\r]?\n~', "\r", $buffer);
1690
			};
1691
		else
1692
			$callback = function ($buffer)
1693
			{
1694
				return preg_replace('~[\r]?\n~', "\n", $buffer);
1695
			};
1696
	}
1697
1698
	// Since we don't do output compression for files this large...
1699
	if (filesize($filename) > 4194304)
1700
	{
1701
		// Forcibly end any output buffering going on.
1702
		while (@ob_get_level() > 0)
1703
			@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1704
1705
		$fp = fopen($filename, 'rb');
1706
		while (!feof($fp))
1707
		{
1708
			if (isset($callback))
1709
				echo $callback(fread($fp, 8192));
1710
			else
1711
				echo fread($fp, 8192);
1712
			flush();
1713
		}
1714
		fclose($fp);
1715
	}
1716
	// On some of the less-bright hosts, readfile() is disabled.  It's just a faster, more byte safe, version of what's in the if.
1717
	elseif (isset($callback) || @readfile($filename) === null)
1718
		echo isset($callback) ? $callback(file_get_contents($filename)) : file_get_contents($filename);
1719
1720
	obExit(false);
1721
}
1722
1723
/**
1724
 * A sort function for putting unapproved attachments first.
1725
 * @param array $a An array of info about one attachment
1726
 * @param array $b An array of info about a second attachment
1727
 * @return int -1 if $a is approved but $b isn't, 0 if both are approved/unapproved, 1 if $b is approved but a isn't
1728
 */
1729
function approved_attach_sort($a, $b)
1730
{
1731
	if ($a['is_approved'] == $b['is_approved'])
1732
		return 0;
1733
1734
	return $a['is_approved'] > $b['is_approved'] ? -1 : 1;
1735
}
1736
1737
/**
1738
 * In-topic quick moderation.
1739
 */
1740
function QuickInTopicModeration()
1741
{
1742
	global $sourcedir, $topic, $board, $user_info, $smcFunc, $modSettings, $context;
1743
1744
	// Check the session = get or post.
1745
	checkSession('request');
1746
1747
	require_once($sourcedir . '/RemoveTopic.php');
1748
1749
	if (empty($_REQUEST['msgs']))
1750
		redirectexit('topic=' . $topic . '.' . $_REQUEST['start']);
1751
1752
	$messages = array();
1753
	foreach ($_REQUEST['msgs'] as $dummy)
1754
		$messages[] = (int) $dummy;
1755
1756
	// We are restoring messages. We handle this in another place.
1757 View Code Duplication
	if (isset($_REQUEST['restore_selected']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1758
		redirectexit('action=restoretopic;msgs=' . implode(',', $messages) . ';' . $context['session_var'] . '=' . $context['session_id']);
1759
	if (isset($_REQUEST['split_selection']))
1760
	{
1761
		$request = $smcFunc['db_query']('', '
1762
			SELECT subject
1763
			FROM {db_prefix}messages
1764
			WHERE id_msg = {int:message}
1765
			LIMIT 1',
1766
			array(
1767
				'message' => min($messages),
1768
			)
1769
		);
1770
		list($subname) = $smcFunc['db_fetch_row']($request);
1771
		$smcFunc['db_free_result']($request);
1772
		$_SESSION['split_selection'][$topic] = $messages;
1773
		redirectexit('action=splittopics;sa=selectTopics;topic=' . $topic . '.0;subname_enc=' .urlencode($subname) . ';' . $context['session_var'] . '=' . $context['session_id']);
1774
	}
1775
1776
	// Allowed to delete any message?
1777
	if (allowedTo('delete_any'))
1778
		$allowed_all = true;
1779
	// Allowed to delete replies to their messages?
1780 View Code Duplication
	elseif (allowedTo('delete_replies'))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1781
	{
1782
		$request = $smcFunc['db_query']('', '
1783
			SELECT id_member_started
1784
			FROM {db_prefix}topics
1785
			WHERE id_topic = {int:current_topic}
1786
			LIMIT 1',
1787
			array(
1788
				'current_topic' => $topic,
1789
			)
1790
		);
1791
		list ($starter) = $smcFunc['db_fetch_row']($request);
1792
		$smcFunc['db_free_result']($request);
1793
1794
		$allowed_all = $starter == $user_info['id'];
1795
	}
1796
	else
1797
		$allowed_all = false;
1798
1799
	// Make sure they're allowed to delete their own messages, if not any.
1800
	if (!$allowed_all)
1801
		isAllowedTo('delete_own');
1802
1803
	// Allowed to remove which messages?
1804
	$request = $smcFunc['db_query']('', '
1805
		SELECT id_msg, subject, id_member, poster_time
1806
		FROM {db_prefix}messages
1807
		WHERE id_msg IN ({array_int:message_list})
1808
			AND id_topic = {int:current_topic}' . (!$allowed_all ? '
1809
			AND id_member = {int:current_member}' : '') . '
1810
		LIMIT {int:limit}',
1811
		array(
1812
			'current_member' => $user_info['id'],
1813
			'current_topic' => $topic,
1814
			'message_list' => $messages,
1815
			'limit' => count($messages),
1816
		)
1817
	);
1818
	$messages = array();
1819
	while ($row = $smcFunc['db_fetch_assoc']($request))
1820
	{
1821
		if (!$allowed_all && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time())
1822
			continue;
1823
1824
		$messages[$row['id_msg']] = array($row['subject'], $row['id_member']);
1825
	}
1826
	$smcFunc['db_free_result']($request);
1827
1828
	// Get the first message in the topic - because you can't delete that!
1829
	$request = $smcFunc['db_query']('', '
1830
		SELECT id_first_msg, id_last_msg
1831
		FROM {db_prefix}topics
1832
		WHERE id_topic = {int:current_topic}
1833
		LIMIT 1',
1834
		array(
1835
			'current_topic' => $topic,
1836
		)
1837
	);
1838
	list ($first_message, $last_message) = $smcFunc['db_fetch_row']($request);
1839
	$smcFunc['db_free_result']($request);
1840
1841
	// Delete all the messages we know they can delete. ($messages)
1842
	foreach ($messages as $message => $info)
1843
	{
1844
		// Just skip the first message - if it's not the last.
1845
		if ($message == $first_message && $message != $last_message)
1846
			continue;
1847
		// If the first message is going then don't bother going back to the topic as we're effectively deleting it.
1848
		elseif ($message == $first_message)
1849
			$topicGone = true;
1850
1851
		removeMessage($message);
1852
1853
		// Log this moderation action ;).
1854 View Code Duplication
		if (allowedTo('delete_any') && (!allowedTo('delete_own') || $info[1] != $user_info['id']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1855
			logAction('delete', array('topic' => $topic, 'subject' => $info[0], 'member' => $info[1], 'board' => $board));
1856
	}
1857
1858
	redirectexit(!empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start']);
1859
}
1860
1861
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...