Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

Sources/Display.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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