Completed
Push — release-2.1 ( 5876a2...f30e04 )
by Mert
06:50
created

Display.php ➔ prepareDisplayContext()   F

Complexity

Conditions 67
Paths > 20000

Size

Total Lines 161
Code Lines 96

Duplication

Lines 14
Ratio 8.7 %
Metric Value
cc 67
eloc 96
nc 429496.7295
nop 1
dl 14
loc 161
rs 2

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
			'vote' => array('test' => 'allow_return_vote', 'text' => 'poll_return_vote', 'image' => 'poll_options.png', 'lang' => true, 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start']),
789
			'results' => array('test' => 'show_view_results_button', 'text' => 'poll_results', 'image' => 'poll_results.png', 'lang' => true, 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start'] . ';viewresults'),
790
			'change_vote' => array('test' => 'allow_change_vote', 'text' => 'poll_change_vote', 'image' => 'poll_change_vote.png', 'lang' => true, 'url' => $scripturl . '?action=vote;topic=' . $context['current_topic'] . '.' . $context['start'] . ';poll=' . $context['poll']['id'] . ';' . $context['session_var'] . '=' . $context['session_id']),
791
			'lock' => array('test' => 'allow_lock_poll', 'text' => (!$context['poll']['is_locked'] ? 'poll_lock' : 'poll_unlock'), 'image' => 'poll_lock.png', 'lang' => true, 'url' => $scripturl . '?action=lockvoting;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']),
792
			'edit' => array('test' => 'allow_edit_poll', 'text' => 'poll_edit', 'image' => 'poll_edit.png', 'lang' => true, 'url' => $scripturl . '?action=editpoll;topic=' . $context['current_topic'] . '.' . $context['start']),
793
			'remove_poll' => array('test' => 'can_remove_poll', 'text' => 'poll_remove', 'image' => 'admin_remove_poll.png', 'lang' => true, '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']),
794
		);
795
796
		// Allow mods to add additional buttons here
797
		call_integration_hook('integrate_poll_buttons');
798
	}
799
800
	// Calculate the fastest way to get the messages!
801
	$ascending = empty($options['view_newest_first']);
802
	$start = $_REQUEST['start'];
803
	$limit = $context['messages_per_page'];
804
	$firstIndex = 0;
805
	if ($start >= $context['total_visible_posts'] / 2 && $context['messages_per_page'] != -1)
806
	{
807
		$ascending = !$ascending;
808
		$limit = $context['total_visible_posts'] <= $start + $limit ? $context['total_visible_posts'] - $start : $limit;
809
		$start = $context['total_visible_posts'] <= $start + $limit ? 0 : $context['total_visible_posts'] - $start - $limit;
810
		$firstIndex = $limit - 1;
811
	}
812
813
	// Get each post and poster in this topic.
814
	$request = $smcFunc['db_query']('', '
815
		SELECT id_msg, id_member, approved
816
		FROM {db_prefix}messages
817
		WHERE id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || $approve_posts ? '' : '
818
		AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')') . '
819
		ORDER BY id_msg ' . ($ascending ? '' : 'DESC') . ($context['messages_per_page'] == -1 ? '' : '
820
		LIMIT {int:start}, {int:max}'),
821
		array(
822
			'current_member' => $user_info['id'],
823
			'current_topic' => $topic,
824
			'is_approved' => 1,
825
			'blank_id_member' => 0,
826
			'start' => $start,
827
			'max' => $limit,
828
		)
829
	);
830
831
	$messages = array();
832
	$all_posters = array();
833
	while ($row = $smcFunc['db_fetch_assoc']($request))
834
	{
835
		if (!empty($row['id_member']))
836
			$all_posters[$row['id_msg']] = $row['id_member'];
837
		$messages[] = $row['id_msg'];
838
	}
839
	$smcFunc['db_free_result']($request);
840
	$posters = array_unique($all_posters);
841
842
	call_integration_hook('integrate_display_message_list', array(&$messages, &$posters));
843
844
	// Guests can't mark topics read or for notifications, just can't sorry.
845
	if (!$user_info['is_guest'] && !empty($messages))
846
	{
847
		$mark_at_msg = max($messages);
848
		if ($mark_at_msg >= $context['topicinfo']['id_last_msg'])
849
			$mark_at_msg = $modSettings['maxMsgID'];
850
		if ($mark_at_msg >= $context['topicinfo']['new_from'])
851
		{
852
			$smcFunc['db_insert']($context['topicinfo']['new_from'] == 0 ? 'ignore' : 'replace',
853
				'{db_prefix}log_topics',
854
				array(
855
					'id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'unwatched' => 'int',
856
				),
857
				array(
858
					$user_info['id'], $topic, $mark_at_msg, $context['topicinfo']['unwatched'],
859
				),
860
				array('id_member', 'id_topic')
861
			);
862
		}
863
864
		// Check for notifications on this topic OR board.
865
		$request = $smcFunc['db_query']('', '
866
			SELECT sent, id_topic
867
			FROM {db_prefix}log_notify
868
			WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
869
				AND id_member = {int:current_member}
870
			LIMIT 2',
871
			array(
872
				'current_board' => $board,
873
				'current_member' => $user_info['id'],
874
				'current_topic' => $topic,
875
			)
876
		);
877
		$do_once = true;
878
		while ($row = $smcFunc['db_fetch_assoc']($request))
879
		{
880
			// Find if this topic is marked for notification...
881
			if (!empty($row['id_topic']))
882
				$context['is_marked_notify'] = true;
883
884
			// Only do this once, but mark the notifications as "not sent yet" for next time.
885
			if (!empty($row['sent']) && $do_once)
886
			{
887
				$smcFunc['db_query']('', '
888
					UPDATE {db_prefix}log_notify
889
					SET sent = {int:is_not_sent}
890
					WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
891
						AND id_member = {int:current_member}',
892
					array(
893
						'current_board' => $board,
894
						'current_member' => $user_info['id'],
895
						'current_topic' => $topic,
896
						'is_not_sent' => 0,
897
					)
898
				);
899
				$do_once = false;
900
			}
901
		}
902
903
		// Have we recently cached the number of new topics in this board, and it's still a lot?
904
		if (isset($_REQUEST['topicseen']) && isset($_SESSION['topicseen_cache'][$board]) && $_SESSION['topicseen_cache'][$board] > 5)
905
			$_SESSION['topicseen_cache'][$board]--;
906
		// Mark board as seen if this is the only new topic.
907
		elseif (isset($_REQUEST['topicseen']))
908
		{
909
			// Use the mark read tables... and the last visit to figure out if this should be read or not.
910
			$request = $smcFunc['db_query']('', '
911
				SELECT COUNT(*)
912
				FROM {db_prefix}topics AS t
913
					LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = {int:current_board} AND lb.id_member = {int:current_member})
914
					LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
915
				WHERE t.id_board = {int:current_board}
916
					AND t.id_last_msg > COALESCE(lb.id_msg, 0)
917
					AND t.id_last_msg > COALESCE(lt.id_msg, 0)' . (empty($_SESSION['id_msg_last_visit']) ? '' : '
918
					AND t.id_last_msg > {int:id_msg_last_visit}'),
919
				array(
920
					'current_board' => $board,
921
					'current_member' => $user_info['id'],
922
					'id_msg_last_visit' => (int) $_SESSION['id_msg_last_visit'],
923
				)
924
			);
925
			list ($numNewTopics) = $smcFunc['db_fetch_row']($request);
926
			$smcFunc['db_free_result']($request);
927
928
			// If there're no real new topics in this board, mark the board as seen.
929
			if (empty($numNewTopics))
930
				$_REQUEST['boardseen'] = true;
931
			else
932
				$_SESSION['topicseen_cache'][$board] = $numNewTopics;
933
		}
934
		// Probably one less topic - maybe not, but even if we decrease this too fast it will only make us look more often.
935
		elseif (isset($_SESSION['topicseen_cache'][$board]))
936
			$_SESSION['topicseen_cache'][$board]--;
937
938
		// Mark board as seen if we came using last post link from BoardIndex. (or other places...)
939
		if (isset($_REQUEST['boardseen']))
940
		{
941
			$smcFunc['db_insert']('replace',
942
				'{db_prefix}log_boards',
943
				array('id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'),
944
				array($modSettings['maxMsgID'], $user_info['id'], $board),
945
				array('id_member', 'id_board')
946
			);
947
		}
948
	}
949
950
	// Get notification preferences
951
	$context['topicinfo']['notify_prefs'] = array();
952
	if (!empty($user_info['id']))
953
	{
954
		require_once($sourcedir . '/Subs-Notify.php');
955
		$prefs = getNotifyPrefs($user_info['id'], array('topic_notify', 'topic_notify_' . $context['current_topic']), true);
956
		$pref = !empty($prefs[$user_info['id']]) && $context['is_marked_notify'] ? $prefs[$user_info['id']] : array();
957
		$context['topicinfo']['notify_prefs'] = array(
958
			'is_custom' => isset($pref['topic_notify_' . $topic]),
959
			'pref' => isset($pref['topic_notify_' . $context['current_topic']]) ? $pref['topic_notify_' . $context['current_topic']] : (!empty($pref['topic_notify']) ? $pref['topic_notify'] : 0),
960
		);
961
	}
962
963
	$context['topic_notification'] = !empty($user_info['id']) ? $context['topicinfo']['notify_prefs'] : array();
964
	// 0 => unwatched, 1 => normal, 2 => receive alerts, 3 => receive emails
965
	$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;
966
967
	$context['loaded_attachments'] = array();
968
969
	// If there _are_ messages here... (probably an error otherwise :!)
970
	if (!empty($messages))
971
	{
972
		// Fetch attachments.
973
		if (!empty($modSettings['attachmentEnable']) && allowedTo('view_attachments'))
974
		{
975
			$request = $smcFunc['db_query']('', '
976
				SELECT
977
					a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, COALESCE(a.size, 0) AS filesize, a.downloads, a.approved,
978
					a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
979
					COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
980
				FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
981
					LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
982
				WHERE a.id_msg IN ({array_int:message_list})
983
					AND a.attachment_type = {int:attachment_type}',
984
				array(
985
					'message_list' => $messages,
986
					'attachment_type' => 0,
987
					'is_approved' => 1,
988
				)
989
			);
990
			$temp = array();
991
			while ($row = $smcFunc['db_fetch_assoc']($request))
992
			{
993 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...
994
					continue;
995
996
				$temp[$row['id_attach']] = $row;
997
				$temp[$row['id_attach']]['topic'] = $topic;
998
				$temp[$row['id_attach']]['board'] = $board;
999
1000
				if (!isset($context['loaded_attachments'][$row['id_msg']]))
1001
					$context['loaded_attachments'][$row['id_msg']] = array();
1002
			}
1003
			$smcFunc['db_free_result']($request);
1004
1005
			// This is better than sorting it with the query...
1006
			ksort($temp);
1007
1008
			foreach ($temp as $row)
1009
				$context['loaded_attachments'][$row['id_msg']][] = $row;
1010
		}
1011
1012
		$msg_parameters = array(
1013
			'message_list' => $messages,
1014
			'new_from' => $context['topicinfo']['new_from'],
1015
		);
1016
		$msg_selects = array();
1017
		$msg_tables = array();
1018
		call_integration_hook('integrate_query_message', array(&$msg_selects, &$msg_tables, &$msg_parameters));
1019
1020
		// What?  It's not like it *couldn't* be only guests in this topic...
1021
		loadMemberData($posters);
1022
		$messages_request = $smcFunc['db_query']('', '
1023
			SELECT
1024
				id_msg, icon, subject, poster_time, poster_ip, id_member, modified_time, modified_name, modified_reason, body,
1025
				smileys_enabled, poster_name, poster_email, approved, likes,
1026
				id_msg_modified < {int:new_from} AS is_read
1027
				' . (!empty($msg_selects) ? (', '. implode(', ', $msg_selects)) : '') . '
1028
			FROM {db_prefix}messages
1029
				' . (!empty($msg_tables) ? implode("\n\t", $msg_tables) : '') . '
1030
			WHERE id_msg IN ({array_int:message_list})
1031
			ORDER BY id_msg' . (empty($options['view_newest_first']) ? '' : ' DESC'),
1032
			$msg_parameters
1033
		);
1034
1035
		// And the likes
1036
		if (!empty($modSettings['enable_likes']))
1037
			$context['my_likes'] = $context['user']['is_guest'] ? array() : prepareLikesContext($topic);
1038
1039
		// Go to the last message if the given time is beyond the time of the last message.
1040 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...
1041
			$context['start_from'] = $context['topicinfo']['num_replies'];
1042
1043
		// Since the anchor information is needed on the top of the page we load these variables beforehand.
1044
		$context['first_message'] = isset($messages[$firstIndex]) ? $messages[$firstIndex] : $messages[0];
1045
		if (empty($options['view_newest_first']))
1046
			$context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $context['start_from'];
1047 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...
1048
			$context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $context['topicinfo']['num_replies'] - $context['start_from'];
1049
	}
1050
	else
1051
	{
1052
		$messages_request = false;
1053
		$context['first_message'] = 0;
1054
		$context['first_new_message'] = false;
1055
1056
		$context['likes'] = array();
1057
	}
1058
1059
	$context['jump_to'] = array(
1060
		'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
1061
		'board_name' => $smcFunc['htmlspecialchars'](strtr(strip_tags($board_info['name']), array('&amp;' => '&'))),
1062
		'child_level' => $board_info['child_level'],
1063
	);
1064
1065
	// Set the callback.  (do you REALIZE how much memory all the messages would take?!?)
1066
	// This will be called from the template.
1067
	$context['get_message'] = 'prepareDisplayContext';
1068
1069
	// Now set all the wonderful, wonderful permissions... like moderation ones...
1070
	$common_permissions = array(
1071
		'can_approve' => 'approve_posts',
1072
		'can_ban' => 'manage_bans',
1073
		'can_sticky' => 'make_sticky',
1074
		'can_merge' => 'merge_any',
1075
		'can_split' => 'split_any',
1076
		'calendar_post' => 'calendar_post',
1077
		'can_send_pm' => 'pm_send',
1078
		'can_report_moderator' => 'report_any',
1079
		'can_moderate_forum' => 'moderate_forum',
1080
		'can_issue_warning' => 'issue_warning',
1081
		'can_restore_topic' => 'move_any',
1082
		'can_restore_msg' => 'move_any',
1083
		'can_see_likes' => 'likes_view',
1084
		'can_like' => 'likes_like',
1085
	);
1086
	foreach ($common_permissions as $contextual => $perm)
1087
		$context[$contextual] = allowedTo($perm);
1088
1089
	// Permissions with _any/_own versions.  $context[YYY] => ZZZ_any/_own.
1090
	$anyown_permissions = array(
1091
		'can_move' => 'move',
1092
		'can_lock' => 'lock',
1093
		'can_delete' => 'remove',
1094
		'can_add_poll' => 'poll_add',
1095
		'can_remove_poll' => 'poll_remove',
1096
		'can_reply' => 'post_reply',
1097
		'can_reply_unapproved' => 'post_unapproved_replies',
1098
		'can_view_warning' => 'profile_warning',
1099
	);
1100
	foreach ($anyown_permissions as $contextual => $perm)
1101
		$context[$contextual] = allowedTo($perm . '_any') || ($context['user']['started'] && allowedTo($perm . '_own'));
1102
1103
	if (!$user_info['is_admin'] && !$modSettings['topic_move_any'])
1104
	{
1105
		// We'll use this in a minute
1106
		$boards_allowed = array_diff(boardsAllowedTo('post_new'), array($board));
1107
1108
		/* You can't move this unless you have permission
1109
			to start new topics on at least one other board */
1110
		$context['can_move'] &= count($boards_allowed) > 1;
1111
	}
1112
1113
	// If a topic is locked, you can't remove it unless it's yours and you locked it or you can lock_any
1114
	if ($context['topicinfo']['locked'])
1115
	{
1116
		$context['can_delete'] &= (($context['topicinfo']['locked'] == 1 && $context['user']['started']) || allowedTo('lock_any'));
1117
	}
1118
1119
	// Cleanup all the permissions with extra stuff...
1120
	$context['can_mark_notify'] = !$context['user']['is_guest'];
1121
	$context['calendar_post'] &= !empty($modSettings['cal_enabled']);
1122
	$context['can_add_poll'] &= $modSettings['pollMode'] == '1' && $context['topicinfo']['id_poll'] <= 0;
1123
	$context['can_remove_poll'] &= $modSettings['pollMode'] == '1' && $context['topicinfo']['id_poll'] > 0;
1124
	$context['can_reply'] &= empty($context['topicinfo']['locked']) || allowedTo('moderate_board');
1125
	$context['can_reply_unapproved'] &= $modSettings['postmod_active'] && (empty($context['topicinfo']['locked']) || allowedTo('moderate_board'));
1126
	$context['can_issue_warning'] &= $modSettings['warning_settings'][0] == 1;
1127
	// Handle approval flags...
1128
	$context['can_reply_approved'] = $context['can_reply'];
1129
	$context['can_reply'] |= $context['can_reply_unapproved'];
1130
	$context['can_quote'] = $context['can_reply'] && (empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])));
1131
	$context['can_mark_unread'] = !$user_info['is_guest'];
1132
	$context['can_unwatch'] = !$user_info['is_guest'];
1133
	$context['can_set_notify'] = !$user_info['is_guest'];
1134
1135
	$context['can_print'] = empty($modSettings['disable_print_topic']);
1136
1137
	// Start this off for quick moderation - it will be or'd for each post.
1138
	$context['can_remove_post'] = allowedTo('delete_any') || (allowedTo('delete_replies') && $context['user']['started']);
1139
1140
	// Can restore topic?  That's if the topic is in the recycle board and has a previous restore state.
1141
	$context['can_restore_topic'] &= !empty($board_info['recycle']) && !empty($context['topicinfo']['id_previous_board']);
1142
	$context['can_restore_msg'] &= !empty($board_info['recycle']) && !empty($context['topicinfo']['id_previous_topic']);
1143
1144
	// Check if the draft functions are enabled and that they have permission to use them (for quick reply.)
1145
	$context['drafts_save'] = !empty($modSettings['drafts_post_enabled']) && allowedTo('post_draft') && $context['can_reply'];
1146
	$context['drafts_autosave'] = !empty($context['drafts_save']) && !empty($modSettings['drafts_autosave_enabled']);
1147
	if (!empty($context['drafts_save']))
1148
		loadLanguage('Drafts');
1149
1150
	// When was the last time this topic was replied to?  Should we warn them about it?
1151
	if (!empty($modSettings['oldTopicDays']) && ($context['can_reply'] || $context['can_reply_unapproved']) && empty($context['topicinfo']['is_sticky']))
1152
	{
1153
		$request = $smcFunc['db_query']('', '
1154
			SELECT poster_time
1155
			FROM {db_prefix}messages
1156
			WHERE id_msg = {int:id_last_msg}
1157
			LIMIT 1',
1158
			array(
1159
				'id_last_msg' => $context['topicinfo']['id_last_msg'],
1160
			)
1161
		);
1162
1163
		list ($lastPostTime) = $smcFunc['db_fetch_row']($request);
1164
		$smcFunc['db_free_result']($request);
1165
1166
		$context['oldTopicError'] = $lastPostTime + $modSettings['oldTopicDays'] * 86400 < time();
1167
	}
1168
1169
	// You can't link an existing topic to the calendar unless you can modify the first post...
1170
	$context['calendar_post'] &= allowedTo('modify_any') || (allowedTo('modify_own') && $context['user']['started']);
1171
1172
	// Load up the "double post" sequencing magic.
1173
	checkSubmitOnce('register');
1174
	$context['name'] = isset($_SESSION['guest_name']) ? $_SESSION['guest_name'] : '';
1175
	$context['email'] = isset($_SESSION['guest_email']) ? $_SESSION['guest_email'] : '';
1176
	// Needed for the editor and message icons.
1177
	require_once($sourcedir . '/Subs-Editor.php');
1178
1179
	// Now create the editor.
1180
	$editorOptions = array(
1181
		'id' => 'quickReply',
1182
		'value' => '',
1183
		'disable_smiley_box' => empty($options['use_editor_quick_reply']),
1184
		'labels' => array(
1185
			'post_button' => $txt['post'],
1186
		),
1187
		// add height and width for the editor
1188
		'height' => '250px',
1189
		'width' => '100%',
1190
		// We do HTML preview here.
1191
		'preview_type' => 1,
1192
		// This is required
1193
		'required' => true,
1194
	);
1195
	create_control_richedit($editorOptions);
1196
1197
	// Store the ID.
1198
	$context['post_box_name'] = $editorOptions['id'];
1199
1200
	// Set a flag so the sub template knows what to do...
1201
	$context['show_bbc'] = !empty($options['use_editor_quick_reply']);
1202
	$modSettings['disable_wysiwyg'] = !empty($options['use_editor_quick_reply']);
1203
	$context['attached'] = '';
1204
	$context['make_poll'] = isset($_REQUEST['poll']);
1205
1206
	// Message icons - customized icons are off?
1207
	$context['icons'] = getMessageIcons($board);
1208
1209 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...
1210
		$context['icons'][count($context['icons']) - 1]['is_last'] = true;
1211
1212
	// Build the normal button array.
1213
	$context['normal_buttons'] = array(
1214
		'reply' => array('test' => 'can_reply', 'text' => 'reply', 'image' => 'reply.png', 'lang' => true, 'url' => $scripturl . '?action=post;topic=' . $context['current_topic'] . '.' . $context['start'] . ';last_msg=' . $context['topic_last_message'], 'active' => true),
1215
		'add_poll' => array('test' => 'can_add_poll', 'text' => 'add_poll', 'image' => 'add_poll.png', 'lang' => true, 'url' => $scripturl . '?action=editpoll;add;topic=' . $context['current_topic'] . '.' . $context['start']),
1216
		'mark_unread' => array('test' => 'can_mark_unread', 'text' => 'mark_unread', 'image' => 'markunread.png', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=topic;t=' . $context['mark_unread_time'] . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']),
1217
		'print' => array('test' => 'can_print', 'text' => 'print', 'image' => 'print.png', 'lang' => true, 'custom' => 'rel="nofollow"', 'url' => $scripturl . '?action=printpage;topic=' . $context['current_topic'] . '.0'),
1218
		'notify' => array(
1219
			'lang' => true,
1220
			'test' => 'can_set_notify',
1221
			'text' => 'notify_topic_' . $context['topic_notification_mode'],
1222
			'sub_buttons' => array(
1223
				array(
1224
					'test' => 'can_unwatch',
1225
					'text' => 'notify_topic_0',
1226
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=0;' . $context['session_var'] . '=' . $context['session_id'],
1227
				),
1228
				array(
1229
					'text' => 'notify_topic_1',
1230
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=1;' . $context['session_var'] . '=' . $context['session_id'],
1231
				),
1232
				array(
1233
					'text' => 'notify_topic_2',
1234
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=2;' . $context['session_var'] . '=' . $context['session_id'],
1235
				),
1236
				array(
1237
					'text' => 'notify_topic_3',
1238
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=3;' . $context['session_var'] . '=' . $context['session_id'],
1239
				),
1240
			),
1241
		),
1242
	);
1243
1244
	// Build the mod button array
1245
	$context['mod_buttons'] = array(
1246
		'move' => array('test' => 'can_move', 'text' => 'move_topic', 'image' => 'admin_move.png', 'lang' => true, 'url' => $scripturl . '?action=movetopic;current_board=' . $context['current_board'] . ';topic=' . $context['current_topic'] . '.0'),
1247
		'delete' => array('test' => 'can_delete', 'text' => 'remove_topic', 'image' => 'admin_rem.png', 'lang' => true, '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']),
1248
		'lock' => array('test' => 'can_lock', 'text' => empty($context['is_locked']) ? 'set_lock' : 'set_unlock', 'image' => 'admin_lock.png', 'lang' => true, 'url' => $scripturl . '?action=lock;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']),
1249
		'sticky' => array('test' => 'can_sticky', 'text' => empty($context['is_sticky']) ? 'set_sticky' : 'set_nonsticky', 'image' => 'admin_sticky.png', 'lang' => true, 'url' => $scripturl . '?action=sticky;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']),
1250
		'merge' => array('test' => 'can_merge', 'text' => 'merge', 'image' => 'merge.png', 'lang' => true, 'url' => $scripturl . '?action=mergetopics;board=' . $context['current_board'] . '.0;from=' . $context['current_topic']),
1251
		'calendar' => array('test' => 'calendar_post', 'text' => 'calendar_link', 'image' => 'linktocal.png', 'lang' => true, 'url' => $scripturl . '?action=post;calendar;msg=' . $context['topic_first_message'] . ';topic=' . $context['current_topic'] . '.0'),
1252
	);
1253
1254
	// Restore topic. eh?  No monkey business.
1255
	if ($context['can_restore_topic'])
1256
		$context['mod_buttons'][] = array('text' => 'restore_topic', 'image' => '', 'lang' => true, 'url' => $scripturl . '?action=restoretopic;topics=' . $context['current_topic'] . ';' . $context['session_var'] . '=' . $context['session_id']);
1257
1258
	// Show a message in case a recently posted message became unapproved.
1259
	$context['becomesUnapproved'] = !empty($_SESSION['becomesUnapproved']) ? true : false;
1260
1261
	// Don't want to show this forever...
1262
	if ($context['becomesUnapproved'])
1263
		unset($_SESSION['becomesUnapproved']);
1264
1265
	// Allow adding new mod buttons easily.
1266
	// Note: $context['normal_buttons'] and $context['mod_buttons'] are added for backward compatibility with 2.0, but are deprecated and should not be used
1267
	call_integration_hook('integrate_display_buttons', array(&$context['normal_buttons']));
1268
	// Note: integrate_mod_buttons is no more necessary and deprecated, but is kept for backward compatibility with 2.0
1269
	call_integration_hook('integrate_mod_buttons', array(&$context['mod_buttons']));
1270
1271
	// Load the drafts js file
1272
	if ($context['drafts_autosave'])
1273
		loadJavascriptFile('drafts.js', array('defer' => false), 'smf_drafts');
1274
1275
	// Spellcheck
1276
	if ($context['show_spellchecking'])
1277
		loadJavascriptFile('spellcheck.js', array('defer' => false), 'smf_spellcheck');
1278
1279
	// topic.js
1280
	loadJavascriptFile('topic.js', array('defer' => false), 'smf_topic');
1281
1282
	// quotedText.js
1283
	loadJavascriptFile('quotedText.js', array('defer' => true), 'smf_quotedText');
1284
1285
	// Mentions
1286 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...
1287
	{
1288
		loadJavascriptFile('jquery.atwho.min.js', array('defer' => true), 'smf_atwho');
1289
		loadJavascriptFile('jquery.caret.min.js', array('defer' => true), 'smf_caret');
1290
		loadJavascriptFile('mentions.js', array('defer' => true), 'smf_mentions');
1291
	}
1292
}
1293
1294
/**
1295
 * Callback for the message display.
1296
 * It actually gets and prepares the message context.
1297
 * This function will start over from the beginning if reset is set to true, which is
1298
 * useful for showing an index before or after the posts.
1299
 *
1300
 * @param bool $reset Whether or not to reset the db seek pointer
1301
 * @return array A large array of contextual data for the posts
1302
 */
1303
function prepareDisplayContext($reset = false)
1304
{
1305
	global $settings, $txt, $modSettings, $scripturl, $options, $user_info, $smcFunc;
1306
	global $memberContext, $context, $messages_request, $topic, $board_info, $sourcedir;
1307
1308
	static $counter = null;
1309
1310
	// If the query returned false, bail.
1311
	if ($messages_request == false)
1312
		return false;
1313
1314
	// Remember which message this is.  (ie. reply #83)
1315
	if ($counter === null || $reset)
1316
		$counter = empty($options['view_newest_first']) ? $context['start'] : $context['total_visible_posts'] - $context['start'];
1317
1318
	// Start from the beginning...
1319
	if ($reset)
1320
		return @$smcFunc['db_data_seek']($messages_request, 0);
1321
1322
	// Attempt to get the next message.
1323
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1324
	if (!$message)
1325
	{
1326
		$smcFunc['db_free_result']($messages_request);
1327
		return false;
1328
	}
1329
1330
	// $context['icon_sources'] says where each icon should come from - here we set up the ones which will always exist!
1331
	if (empty($context['icon_sources']))
1332
	{
1333
		$context['icon_sources'] = array();
1334
		foreach ($context['stable_icons'] as $icon)
1335
			$context['icon_sources'][$icon] = 'images_url';
1336
	}
1337
1338
	// Message Icon Management... check the images exist.
1339
	if (empty($modSettings['messageIconChecks_disable']))
1340
	{
1341
		// If the current icon isn't known, then we need to do something...
1342 View Code Duplication
		if (!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...
1343
			$context['icon_sources'][$message['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['icon'] . '.png') ? 'images_url' : 'default_images_url';
1344
	}
1345 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...
1346
		$context['icon_sources'][$message['icon']] = 'images_url';
1347
1348
	// If you're a lazy bum, you probably didn't give a subject...
1349
	$message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject'];
1350
1351
	// Are you allowed to remove at least a single reply?
1352
	$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'];
1353
1354
	// If the topic is locked, you might not be able to delete the post...
1355
	if ($context['is_locked'])
1356
	{
1357
		$context['can_remove_post'] &= ($context['user']['started'] && $context['is_locked'] == 1) || allowedTo('lock_any');
1358
	}
1359
1360
	// If it couldn't load, or the user was a guest.... someday may be done with a guest table.
1361
	if (!loadMemberContext($message['id_member'], true))
1362
	{
1363
		// Notice this information isn't used anywhere else....
1364
		$memberContext[$message['id_member']]['name'] = $message['poster_name'];
1365
		$memberContext[$message['id_member']]['id'] = 0;
1366
		$memberContext[$message['id_member']]['group'] = $txt['guest_title'];
1367
		$memberContext[$message['id_member']]['link'] = $message['poster_name'];
1368
		$memberContext[$message['id_member']]['email'] = $message['poster_email'];
1369
		$memberContext[$message['id_member']]['show_email'] = allowedTo('moderate_forum');
1370
		$memberContext[$message['id_member']]['is_guest'] = true;
1371
	}
1372
	else
1373
	{
1374
		// Define this here to make things a bit more readable
1375
		$can_view_warning = $context['user']['can_mod'] || allowedTo('view_warning_any') || ($message['id_member'] == $user_info['id'] && allowedTo('view_warning_own'));
1376
1377
		$memberContext[$message['id_member']]['can_view_profile'] = allowedTo('profile_view') || ($message['id_member'] == $user_info['id'] && !$user_info['is_guest']);
1378
		$memberContext[$message['id_member']]['is_topic_starter'] = $message['id_member'] == $context['topic_starter_id'];
1379
		$memberContext[$message['id_member']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member']]['warning_status'] && $can_view_warning;
1380
		// Show the email if it's your post...
1381
		$memberContext[$message['id_member']]['show_email'] |= ($message['id_member'] == $user_info['id']);
1382
	}
1383
1384
	$memberContext[$message['id_member']]['ip'] = inet_dtop($message['poster_ip']);
1385
	$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']);
1386
1387
	// Do the censor thang.
1388
	censorText($message['body']);
1389
	censorText($message['subject']);
1390
1391
	// Run BBC interpreter on the message.
1392
	$message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1393
1394
	// If it's in the recycle bin we need to override whatever icon we did have.
1395
	if (!empty($board_info['recycle']))
1396
		$message['icon'] = 'recycled';
1397
1398
	require_once($sourcedir . '/Subs-Attachments.php');
1399
1400
	// Compose the memory eat- I mean message array.
1401
	$output = array(
1402
		'attachment' => loadAttachmentContext($message['id_msg'], $context['loaded_attachments']),
1403
		'id' => $message['id_msg'],
1404
		'href' => $scripturl . '?topic=' . $topic . '.msg' . $message['id_msg'] . '#msg' . $message['id_msg'],
1405
		'link' => '<a href="' . $scripturl . '?msg=' . $message['id_msg'] . '" rel="nofollow">' . $message['subject'] . '</a>',
1406
		'member' => &$memberContext[$message['id_member']],
1407
		'icon' => $message['icon'],
1408
		'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.png',
1409
		'subject' => $message['subject'],
1410
		'time' => timeformat($message['poster_time']),
1411
		'timestamp' => forum_time(true, $message['poster_time']),
1412
		'counter' => $counter,
1413
		'modified' => array(
1414
			'time' => timeformat($message['modified_time']),
1415
			'timestamp' => forum_time(true, $message['modified_time']),
1416
			'name' => $message['modified_name'],
1417
			'reason' => $message['modified_reason']
1418
		),
1419
		'body' => $message['body'],
1420
		'new' => empty($message['is_read']),
1421
		'approved' => $message['approved'],
1422
		'first_new' => isset($context['start_from']) && $context['start_from'] == $counter,
1423
		'is_ignored' => !empty($modSettings['enable_buddylist']) && !empty($options['posts_apply_ignore_list']) && in_array($message['id_member'], $context['user']['ignoreusers']),
1424
		'can_approve' => !$message['approved'] && $context['can_approve'],
1425
		'can_unapprove' => !empty($modSettings['postmod_active']) && $context['can_approve'] && $message['approved'],
1426
		'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()))),
1427
		'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())),
1428
		'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member'] == $user_info['id'] && !empty($user_info['id'])),
1429
		'css_class' => $message['approved'] ? 'windowbg' : 'approvebg',
1430
	);
1431
1432
	// Are likes enable?
1433 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...
1434
		$output['likes'] = array(
1435
			'count' => $message['likes'],
1436
			'you' => in_array($message['id_msg'], $context['my_likes']),
1437
			'can_like' => !$context['user']['is_guest'] && $message['id_member'] != $context['user']['id'] && !empty($context['can_like']),
1438
		);
1439
1440
	// Is this user the message author?
1441
	$output['is_message_author'] = $message['id_member'] == $user_info['id'];
1442 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...
1443
		$output['modified']['last_edit_text'] = sprintf($txt['last_edit_by'], $output['modified']['time'], $output['modified']['name']);
1444
1445
	// Did they give a reason for editing?
1446 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...
1447
		$output['modified']['last_edit_text'] .= '&nbsp;' . sprintf($txt['last_edit_reason'], $output['modified']['reason']);
1448
1449
	// Any custom profile fields?
1450
	if (!empty($memberContext[$message['id_member']]['custom_fields']))
1451
		foreach ($memberContext[$message['id_member']]['custom_fields'] as $custom)
1452
			$output['custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
1453
1454
	if (empty($options['view_newest_first']))
1455
		$counter++;
1456
1457
	else
1458
		$counter--;
1459
1460
	call_integration_hook('integrate_prepare_display_context', array(&$output, &$message, $counter));
1461
1462
	return $output;
1463
}
1464
1465
/**
1466
 * Downloads an attachment, and increments the download count.
1467
 * It requires the view_attachments permission.
1468
 * It disables the session parser, and clears any previous output.
1469
 * It depends on the attachmentUploadDir setting being correct.
1470
 * It is accessed via the query string ?action=dlattach.
1471
 * Views to attachments do not increase hits and are not logged in the "Who's Online" log.
1472
 * Legacy code, all attachments are now handled by ShowAttachments.php
1473
 */
1474
function Download()
1475
{
1476
	global $txt, $modSettings, $user_info, $context, $topic, $smcFunc;
1477
1478
	// Some defaults that we need.
1479
	$context['character_set'] = empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'ISO-8859-1' : $txt['lang_character_set']) : $modSettings['global_character_set'];
1480
	$context['utf8'] = $context['character_set'] === 'UTF-8';
1481
	$context['no_last_modified'] = true;
1482
1483
	// Prevent a preview image from being displayed twice.
1484
	if (isset($_GET['action']) && $_GET['action'] == 'dlattach' && isset($_GET['type']) && ($_GET['type'] == 'avatar' || $_GET['type'] == 'preview'))
1485
		return;
1486
1487
	// Make sure some attachment was requested!
1488
	if (!isset($_REQUEST['attach']) && !isset($_REQUEST['id']))
1489
		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...
1490
1491
	$_REQUEST['attach'] = isset($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : (int) $_REQUEST['id'];
1492
1493
	// Do we have a hook wanting to use our attachment system? We use $attachRequest to prevent accidental usage of $request.
1494
	$attachRequest = null;
1495
	call_integration_hook('integrate_download_request', array(&$attachRequest));
1496
	if (!is_null($attachRequest) && $smcFunc['db_is_resource']($attachRequest))
1497
		$request = $attachRequest;
1498
	else
1499
	{
1500
		// This checks only the current board for $board/$topic's permissions.
1501
		isAllowedTo('view_attachments');
1502
1503
		// Make sure this attachment is on this board.
1504
		// @todo: We must verify that $topic is the attachment's topic, or else the permission check above is broken.
1505
		$request = $smcFunc['db_query']('', '
1506
			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
1507
			FROM {db_prefix}attachments AS a
1508
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
1509
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1510
			WHERE a.id_attach = {int:attach}
1511
			LIMIT 1',
1512
			array(
1513
				'attach' => $_REQUEST['attach'],
1514
				'current_topic' => $topic,
1515
			)
1516
		);
1517
	}
1518
1519
	if ($smcFunc['db_num_rows']($request) == 0)
1520
		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...
1521
1522
	list ($id_folder, $real_filename, $file_hash, $file_ext, $id_attach, $attachment_type, $mime_type, $is_approved, $id_member) = $smcFunc['db_fetch_row']($request);
1523
	$smcFunc['db_free_result']($request);
1524
1525
	// If it isn't yet approved, do they have permission to view it?
1526
	if (!$is_approved && ($id_member == 0 || $user_info['id'] != $id_member) && ($attachment_type == 0 || $attachment_type == 3))
1527
		isAllowedTo('approve_posts');
1528
1529
	// Update the download counter (unless it's a thumbnail).
1530
	if ($attachment_type != 3)
1531
		$smcFunc['db_query']('attach_download_increase', '
1532
			UPDATE LOW_PRIORITY {db_prefix}attachments
1533
			SET downloads = downloads + 1
1534
			WHERE id_attach = {int:id_attach}',
1535
			array(
1536
				'id_attach' => $id_attach,
1537
			)
1538
		);
1539
1540
	$filename = getAttachmentFilename($real_filename, $_REQUEST['attach'], $id_folder, false, $file_hash);
1541
1542
	// This is done to clear any output that was made before now.
1543
	ob_end_clean();
1544
	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')))
1545
		@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...
1546
1547
	else
1548
	{
1549
		ob_start();
1550
		header('Content-Encoding: none');
1551
	}
1552
1553
	// No point in a nicer message, because this is supposed to be an attachment anyway...
1554 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...
1555
	{
1556
		header((preg_match('~HTTP/1\.[01]~i', $_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0') . ' 404 Not Found');
1557
		header('Content-Type: text/plain; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
1558
1559
		// We need to die like this *before* we send any anti-caching headers as below.
1560
		die('File not found.');
1561
	}
1562
1563
	// If it hasn't been modified since the last time this attachment was retrieved, there's no need to display it again.
1564 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...
1565
	{
1566
		list($modified_since) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
1567
		if (strtotime($modified_since) >= filemtime($filename))
1568
		{
1569
			ob_end_clean();
1570
1571
			// Answer the question - no, it hasn't been modified ;).
1572
			header('HTTP/1.1 304 Not Modified');
1573
			exit;
1574
		}
1575
	}
1576
1577
	// Check whether the ETag was sent back, and cache based on that...
1578
	$eTag = '"' . substr($_REQUEST['attach'] . $real_filename . filemtime($filename), 0, 64) . '"';
1579 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...
1580
	{
1581
		ob_end_clean();
1582
1583
		header('HTTP/1.1 304 Not Modified');
1584
		exit;
1585
	}
1586
1587
	// Send the attachment headers.
1588
	header('Pragma: ');
1589
1590
	if (!isBrowser('gecko'))
1591
		header('Content-Transfer-Encoding: binary');
1592
1593
	header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT');
1594
	header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filename)) . ' GMT');
1595
	header('Accept-Ranges: bytes');
1596
	header('Connection: close');
1597
	header('ETag: ' . $eTag);
1598
1599
	// Make sure the mime type warrants an inline display.
1600
	if (isset($_REQUEST['image']) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0)
1601
		unset($_REQUEST['image']);
1602
1603
	// Does this have a mime type?
1604
	elseif (!empty($mime_type) && (isset($_REQUEST['image']) || !in_array($file_ext, array('jpg', 'gif', 'jpeg', 'x-ms-bmp', 'png', 'psd', 'tiff', 'iff'))))
1605
		header('Content-Type: ' . strtr($mime_type, array('image/bmp' => 'image/x-ms-bmp')));
1606
1607 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...
1608
	{
1609
		header('Content-Type: ' . (isBrowser('ie') || isBrowser('opera') ? 'application/octetstream' : 'application/octet-stream'));
1610
		if (isset($_REQUEST['image']))
1611
			unset($_REQUEST['image']);
1612
	}
1613
1614
	// Convert the file to UTF-8, cuz most browsers dig that.
1615
	$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);
1616
	$disposition = !isset($_REQUEST['image']) ? 'attachment' : 'inline';
1617
1618
	// Different browsers like different standards...
1619 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...
1620
		header('Content-Disposition: ' . $disposition . '; filename*=UTF-8\'\'' . rawurlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name)));
1621
1622
	elseif (isBrowser('opera'))
1623
		header('Content-Disposition: ' . $disposition . '; filename="' . preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name) . '"');
1624
1625
	elseif (isBrowser('ie'))
1626
		header('Content-Disposition: ' . $disposition . '; filename="' . urlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name)) . '"');
1627
1628
	else
1629
		header('Content-Disposition: ' . $disposition . '; filename="' . $utf8name . '"');
1630
1631
	// If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE.
1632 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...
1633
		header('Cache-Control: no-cache');
1634
	else
1635
		header('Cache-Control: max-age=' . (525600 * 60) . ', private');
1636
1637
	header('Content-Length: ' . filesize($filename));
1638
1639
	// Try to buy some time...
1640
	@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...
1641
1642
	// Recode line endings for text files, if enabled.
1643 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...
1644
	{
1645
		if (strpos($_SERVER['HTTP_USER_AGENT'], 'Windows') !== false)
1646
			$callback = function ($buffer)
1647
			{
1648
				return preg_replace('~[\r]?\n~', "\r\n", $buffer);
1649
			};
1650
		elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') !== false)
1651
			$callback = function ($buffer)
1652
			{
1653
				return preg_replace('~[\r]?\n~', "\r", $buffer);
1654
			};
1655
		else
1656
			$callback = function ($buffer)
1657
			{
1658
				return preg_replace('~[\r]?\n~', "\n", $buffer);
1659
			};
1660
	}
1661
1662
	// Since we don't do output compression for files this large...
1663
	if (filesize($filename) > 4194304)
1664
	{
1665
		// Forcibly end any output buffering going on.
1666
		while (@ob_get_level() > 0)
1667
			@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...
1668
1669
		$fp = fopen($filename, 'rb');
1670
		while (!feof($fp))
1671
		{
1672
			if (isset($callback))
1673
				echo $callback(fread($fp, 8192));
1674
			else
1675
				echo fread($fp, 8192);
1676
			flush();
1677
		}
1678
		fclose($fp);
1679
	}
1680
	// 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.
1681
	elseif (isset($callback) || @readfile($filename) === null)
1682
		echo isset($callback) ? $callback(file_get_contents($filename)) : file_get_contents($filename);
1683
1684
	obExit(false);
1685
}
1686
1687
/**
1688
 * A sort function for putting unapproved attachments first.
1689
 * @param array $a An array of info about one attachment
1690
 * @param array $b An array of info about a second attachment
1691
 * @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
1692
 */
1693
function approved_attach_sort($a, $b)
1694
{
1695
	if ($a['is_approved'] == $b['is_approved'])
1696
		return 0;
1697
1698
	return $a['is_approved'] > $b['is_approved'] ? -1 : 1;
1699
}
1700
1701
/**
1702
 * In-topic quick moderation.
1703
 */
1704
function QuickInTopicModeration()
1705
{
1706
	global $sourcedir, $topic, $board, $user_info, $smcFunc, $modSettings, $context;
1707
1708
	// Check the session = get or post.
1709
	checkSession('request');
1710
1711
	require_once($sourcedir . '/RemoveTopic.php');
1712
1713
	if (empty($_REQUEST['msgs']))
1714
		redirectexit('topic=' . $topic . '.' . $_REQUEST['start']);
1715
1716
	$messages = array();
1717
	foreach ($_REQUEST['msgs'] as $dummy)
1718
		$messages[] = (int) $dummy;
1719
1720
	// We are restoring messages. We handle this in another place.
1721 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...
1722
		redirectexit('action=restoretopic;msgs=' . implode(',', $messages) . ';' . $context['session_var'] . '=' . $context['session_id']);
1723
	if (isset($_REQUEST['split_selection']))
1724
	{
1725
		$request = $smcFunc['db_query']('', '
1726
			SELECT subject
1727
			FROM {db_prefix}messages
1728
			WHERE id_msg = {int:message}
1729
			LIMIT 1',
1730
			array(
1731
				'message' => min($messages),
1732
			)
1733
		);
1734
		list($subname) = $smcFunc['db_fetch_row']($request);
1735
		$smcFunc['db_free_result']($request);
1736
		$_SESSION['split_selection'][$topic] = $messages;
1737
		redirectexit('action=splittopics;sa=selectTopics;topic=' . $topic . '.0;subname_enc=' .urlencode($subname) . ';' . $context['session_var'] . '=' . $context['session_id']);
1738
	}
1739
1740
	// Allowed to delete any message?
1741
	if (allowedTo('delete_any'))
1742
		$allowed_all = true;
1743
	// Allowed to delete replies to their messages?
1744 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...
1745
	{
1746
		$request = $smcFunc['db_query']('', '
1747
			SELECT id_member_started
1748
			FROM {db_prefix}topics
1749
			WHERE id_topic = {int:current_topic}
1750
			LIMIT 1',
1751
			array(
1752
				'current_topic' => $topic,
1753
			)
1754
		);
1755
		list ($starter) = $smcFunc['db_fetch_row']($request);
1756
		$smcFunc['db_free_result']($request);
1757
1758
		$allowed_all = $starter == $user_info['id'];
1759
	}
1760
	else
1761
		$allowed_all = false;
1762
1763
	// Make sure they're allowed to delete their own messages, if not any.
1764
	if (!$allowed_all)
1765
		isAllowedTo('delete_own');
1766
1767
	// Allowed to remove which messages?
1768
	$request = $smcFunc['db_query']('', '
1769
		SELECT id_msg, subject, id_member, poster_time
1770
		FROM {db_prefix}messages
1771
		WHERE id_msg IN ({array_int:message_list})
1772
			AND id_topic = {int:current_topic}' . (!$allowed_all ? '
1773
			AND id_member = {int:current_member}' : '') . '
1774
		LIMIT {int:limit}',
1775
		array(
1776
			'current_member' => $user_info['id'],
1777
			'current_topic' => $topic,
1778
			'message_list' => $messages,
1779
			'limit' => count($messages),
1780
		)
1781
	);
1782
	$messages = array();
1783
	while ($row = $smcFunc['db_fetch_assoc']($request))
1784
	{
1785
		if (!$allowed_all && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time())
1786
			continue;
1787
1788
		$messages[$row['id_msg']] = array($row['subject'], $row['id_member']);
1789
	}
1790
	$smcFunc['db_free_result']($request);
1791
1792
	// Get the first message in the topic - because you can't delete that!
1793
	$request = $smcFunc['db_query']('', '
1794
		SELECT id_first_msg, id_last_msg
1795
		FROM {db_prefix}topics
1796
		WHERE id_topic = {int:current_topic}
1797
		LIMIT 1',
1798
		array(
1799
			'current_topic' => $topic,
1800
		)
1801
	);
1802
	list ($first_message, $last_message) = $smcFunc['db_fetch_row']($request);
1803
	$smcFunc['db_free_result']($request);
1804
1805
	// Delete all the messages we know they can delete. ($messages)
1806
	foreach ($messages as $message => $info)
1807
	{
1808
		// Just skip the first message - if it's not the last.
1809
		if ($message == $first_message && $message != $last_message)
1810
			continue;
1811
		// If the first message is going then don't bother going back to the topic as we're effectively deleting it.
1812
		elseif ($message == $first_message)
1813
			$topicGone = true;
1814
1815
		removeMessage($message);
1816
1817
		// Log this moderation action ;).
1818 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...
1819
			logAction('delete', array('topic' => $topic, 'subject' => $info[0], 'member' => $info[1], 'board' => $board));
1820
	}
1821
1822
	redirectexit(!empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start']);
1823
}
1824
1825
?>
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...
1826