Completed
Push — release-2.1 ( e6c696...22bfba )
by Mathias
07:04
created

Sources/Display.php (5 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 2016 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 3
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/**
21
 * The central part of the board - topic display.
22
 * This function loads the posts in a topic up so they can be displayed.
23
 * It uses the main sub template of the Display template.
24
 * It requires a topic, and can go to the previous or next topic from it.
25
 * It jumps to the correct post depending on a number/time/IS_MSG passed.
26
 * It depends on the messages_per_page, defaultMaxMessages and enableAllMessages settings.
27
 * It is accessed by ?topic=id_topic.START.
28
 * @return void
29
 */
30
function Display()
31
{
32
	global $scripturl, $txt, $modSettings, $context, $settings;
33
	global $options, $sourcedir, $user_info, $board_info, $topic, $board;
34
	global $attachments, $messages_request, $language, $smcFunc;
35
36
	// What are you gonna display if these are empty?!
37
	if (empty($topic))
38
		fatal_lang_error('no_board', false);
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 View Code Duplication
	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 = ms.id_member)' . ($user_info['is_guest'] ? '' : '
167
			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = {int:current_topic} AND lt.id_member = {int:current_member})
168
			LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})') . '
169
			' . (!empty($topic_tables) ? implode("\n\t", $topic_tables) : '') . '
170
		WHERE t.id_topic = {int:current_topic}
171
		LIMIT 1',
172
			$topic_parameters
173
	);
174
175
	if ($smcFunc['db_num_rows']($request) == 0)
176
		fatal_lang_error('not_a_topic', false, 404);
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_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']))
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
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 ? $row['timezone'] : 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
		// If this is a guest we need to do our best to work out if they have voted, and what they voted for.
682 View Code Duplication
		if ($user_info['is_guest'] && $pollinfo['guest_vote'] && allowedTo('poll_vote'))
683
		{
684
			if (!empty($_COOKIE['guest_poll_vote']) && preg_match('~^[0-9,;]+$~', $_COOKIE['guest_poll_vote']) && strpos($_COOKIE['guest_poll_vote'], ';' . $context['topicinfo']['id_poll'] . ',') !== false)
685
			{
686
				// ;id,timestamp,[vote,vote...]; etc
687
				$guestinfo = explode(';', $_COOKIE['guest_poll_vote']);
688
				// Find the poll we're after.
689
				foreach ($guestinfo as $i => $guestvoted)
690
				{
691
					$guestvoted = explode(',', $guestvoted);
692
					if ($guestvoted[0] == $context['topicinfo']['id_poll'])
693
						break;
694
				}
695
				// Has the poll been reset since guest voted?
696
				if ($pollinfo['reset_poll'] > $guestvoted[1])
697
				{
698
					// Remove the poll info from the cookie to allow guest to vote again
699
					unset($guestinfo[$i]);
700
					if (!empty($guestinfo))
701
						$_COOKIE['guest_poll_vote'] = ';' . implode(';', $guestinfo);
702
					else
703
						unset($_COOKIE['guest_poll_vote']);
704
				}
705
				else
706
				{
707
					// What did they vote for?
708
					unset($guestvoted[0], $guestvoted[1]);
709
					foreach ($pollOptions as $choice => $details)
710
					{
711
						$pollOptions[$choice]['voted_this'] = in_array($choice, $guestvoted) ? 1 : -1;
712
						$pollinfo['has_voted'] |= $pollOptions[$choice]['voted_this'] != -1;
713
					}
714
					unset($choice, $details, $guestvoted);
715
				}
716
				unset($guestinfo, $guestvoted, $i);
717
			}
718
		}
719
720
		// Set up the basic poll information.
721
		$context['poll'] = array(
722
			'id' => $context['topicinfo']['id_poll'],
723
			'image' => 'normal_' . (empty($pollinfo['voting_locked']) ? 'poll' : 'locked_poll'),
724
			'question' => parse_bbc($pollinfo['question']),
725
			'total_votes' => $pollinfo['total'],
726
			'change_vote' => !empty($pollinfo['change_vote']),
727
			'is_locked' => !empty($pollinfo['voting_locked']),
728
			'options' => array(),
729
			'lock' => allowedTo('poll_lock_any') || ($context['user']['started'] && allowedTo('poll_lock_own')),
730
			'edit' => allowedTo('poll_edit_any') || ($context['user']['started'] && allowedTo('poll_edit_own')),
731
			'remove' => allowedTo('poll_remove_any') || ($context['user']['started'] && allowedTo('poll_remove_own')),
732
			'allowed_warning' => $pollinfo['max_votes'] > 1 ? sprintf($txt['poll_options6'], min(count($pollOptions), $pollinfo['max_votes'])) : '',
733
			'is_expired' => !empty($pollinfo['expire_time']) && $pollinfo['expire_time'] < time(),
734
			'expire_time' => !empty($pollinfo['expire_time']) ? timeformat($pollinfo['expire_time']) : 0,
735
			'has_voted' => !empty($pollinfo['has_voted']),
736
			'starter' => array(
737
				'id' => $pollinfo['id_member'],
738
				'name' => $row['poster_name'],
739
				'href' => $pollinfo['id_member'] == 0 ? '' : $scripturl . '?action=profile;u=' . $pollinfo['id_member'],
740
				'link' => $pollinfo['id_member'] == 0 ? $row['poster_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $pollinfo['id_member'] . '">' . $row['poster_name'] . '</a>'
741
			)
742
		);
743
744
		// Make the lock, edit and remove permissions defined above more directly accessible.
745
		$context['allow_lock_poll'] = $context['poll']['lock'];
746
		$context['allow_edit_poll'] = $context['poll']['edit'];
747
		$context['can_remove_poll'] = $context['poll']['remove'];
748
749
		// You're allowed to vote if:
750
		// 1. the poll did not expire, and
751
		// 2. you're either not a guest OR guest voting is enabled... and
752
		// 3. you're not trying to view the results, and
753
		// 4. the poll is not locked, and
754
		// 5. you have the proper permissions, and
755
		// 6. you haven't already voted before.
756
		$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'];
757
758
		// You're allowed to view the results if:
759
		// 1. you're just a super-nice-guy, or
760
		// 2. anyone can see them (hide_results == 0), or
761
		// 3. you can see them after you voted (hide_results == 1), or
762
		// 4. you've waited long enough for the poll to expire. (whether hide_results is 1 or 2.)
763
		$context['allow_results_view'] = allowedTo('moderate_board') || $pollinfo['hide_results'] == 0 || ($pollinfo['hide_results'] == 1 && $context['poll']['has_voted']) || $context['poll']['is_expired'];
764
765
		// Show the results if:
766
		// 1. You're allowed to see them (see above), and
767
		// 2. $_REQUEST['viewresults'] or $_REQUEST['viewResults'] is set
768
		$context['poll']['show_results'] = $context['allow_results_view'] && (isset($_REQUEST['viewresults']) || isset($_REQUEST['viewResults']));
769
770
		// Show the button if:
771
		// 1. You can vote in the poll (see above), and
772
		// 2. Results are visible to everyone (hidden = 0), and
773
		// 3. You aren't already viewing the results
774
		$context['show_view_results_button'] = $context['allow_vote'] && $context['allow_results_view'] && !$context['poll']['show_results'];
775
776
		// You're allowed to change your vote if:
777
		// 1. the poll did not expire, and
778
		// 2. you're not a guest... and
779
		// 3. the poll is not locked, and
780
		// 4. you have the proper permissions, and
781
		// 5. you have already voted, and
782
		// 6. the poll creator has said you can!
783
		$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'];
784
785
		// You're allowed to return to voting options if:
786
		// 1. you are (still) allowed to vote.
787
		// 2. you are currently seeing the results.
788
		$context['allow_return_vote'] = $context['allow_vote'] && $context['poll']['show_results'];
789
790
		// Calculate the percentages and bar lengths...
791
		$divisor = $realtotal == 0 ? 1 : $realtotal;
792
793
		// Determine if a decimal point is needed in order for the options to add to 100%.
794
		$precision = $realtotal == 100 ? 0 : 1;
795
796
		// Now look through each option, and...
797
		foreach ($pollOptions as $i => $option)
798
		{
799
			// First calculate the percentage, and then the width of the bar...
800
			$bar = round(($option['votes'] * 100) / $divisor, $precision);
801
			$barWide = $bar == 0 ? 1 : floor(($bar * 8) / 3);
802
803
			// Now add it to the poll's contextual theme data.
804
			$context['poll']['options'][$i] = array(
805
				'id' => 'options-' . $i,
806
				'percent' => $bar,
807
				'votes' => $option['votes'],
808
				'voted_this' => $option['voted_this'] != -1,
809
				'bar_ndt' => $bar > 0 ? '<div class="bar" style="width: ' . $bar . '%;"></div>' : '',
810
				'bar_width' => $barWide,
811
				'option' => parse_bbc($option['label']),
812
				'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') . '">'
813
			);
814
		}
815
816
		// Build the poll moderation button array.
817
		$context['poll_buttons'] = array();
818
819 View Code Duplication
		if ($context['allow_return_vote'])
820
			$context['poll_buttons']['vote'] = array('text' => 'poll_return_vote', 'image' => 'poll_options.png', 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start']);
821
822 View Code Duplication
		if ($context['show_view_results_button'])
823
			$context['poll_buttons']['results'] = array('text' => 'poll_results', 'image' => 'poll_results.png', 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start'] . ';viewresults');
824
825 View Code Duplication
		if ($context['allow_change_vote'])
826
			$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']);
827
828 View Code Duplication
		if ($context['allow_lock_poll'])
829
			$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']);
830
831 View Code Duplication
		if ($context['allow_edit_poll'])
832
			$context['poll_buttons']['edit'] = array('text' => 'poll_edit', 'image' => 'poll_edit.png', 'url' => $scripturl . '?action=editpoll;topic=' . $context['current_topic'] . '.' . $context['start']);
833
834
		if ($context['can_remove_poll'])
835
			$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']);
836
837
		// Allow mods to add additional buttons here
838
		call_integration_hook('integrate_poll_buttons');
839
	}
840
841
	// Calculate the fastest way to get the messages!
842
	$ascending = empty($options['view_newest_first']);
843
	$start = $_REQUEST['start'];
844
	$limit = $context['messages_per_page'];
845
	$firstIndex = 0;
846
	if ($start >= $context['total_visible_posts'] / 2 && $context['messages_per_page'] != -1)
847
	{
848
		$ascending = !$ascending;
849
		$limit = $context['total_visible_posts'] <= $start + $limit ? $context['total_visible_posts'] - $start : $limit;
850
		$start = $context['total_visible_posts'] <= $start + $limit ? 0 : $context['total_visible_posts'] - $start - $limit;
851
		$firstIndex = $limit - 1;
852
	}
853
854
	// Get each post and poster in this topic.
855
	$request = $smcFunc['db_query']('', '
856
		SELECT id_msg, id_member, approved
857
		FROM {db_prefix}messages
858
		WHERE id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || $approve_posts ? '' : '
859
		AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')') . '
860
		ORDER BY id_msg ' . ($ascending ? '' : 'DESC') . ($context['messages_per_page'] == -1 ? '' : '
861
		LIMIT {int:start}, {int:max}'),
862
		array(
863
			'current_member' => $user_info['id'],
864
			'current_topic' => $topic,
865
			'is_approved' => 1,
866
			'blank_id_member' => 0,
867
			'start' => $start,
868
			'max' => $limit,
869
		)
870
	);
871
872
	$messages = array();
873
	$all_posters = array();
874
	while ($row = $smcFunc['db_fetch_assoc']($request))
875
	{
876
		if (!empty($row['id_member']))
877
			$all_posters[$row['id_msg']] = $row['id_member'];
878
		$messages[] = $row['id_msg'];
879
	}
880
	$smcFunc['db_free_result']($request);
881
	$posters = array_unique($all_posters);
882
883
	call_integration_hook('integrate_display_message_list', array(&$messages, &$posters));
884
885
	// Guests can't mark topics read or for notifications, just can't sorry.
886
	if (!$user_info['is_guest'] && !empty($messages))
887
	{
888
		$mark_at_msg = max($messages);
889
		if ($mark_at_msg >= $context['topicinfo']['id_last_msg'])
890
			$mark_at_msg = $modSettings['maxMsgID'];
891
		if ($mark_at_msg >= $context['topicinfo']['new_from'])
892
		{
893
			$smcFunc['db_insert']($context['topicinfo']['new_from'] == 0 ? 'ignore' : 'replace',
894
				'{db_prefix}log_topics',
895
				array(
896
					'id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'unwatched' => 'int',
897
				),
898
				array(
899
					$user_info['id'], $topic, $mark_at_msg, $context['topicinfo']['unwatched'],
900
				),
901
				array('id_member', 'id_topic')
902
			);
903
		}
904
905
		// Check for notifications on this topic OR board.
906
		$request = $smcFunc['db_query']('', '
907
			SELECT sent, id_topic
908
			FROM {db_prefix}log_notify
909
			WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
910
				AND id_member = {int:current_member}
911
			LIMIT 2',
912
			array(
913
				'current_board' => $board,
914
				'current_member' => $user_info['id'],
915
				'current_topic' => $topic,
916
			)
917
		);
918
		$do_once = true;
919
		while ($row = $smcFunc['db_fetch_assoc']($request))
920
		{
921
			// Find if this topic is marked for notification...
922
			if (!empty($row['id_topic']))
923
				$context['is_marked_notify'] = true;
924
925
			// Only do this once, but mark the notifications as "not sent yet" for next time.
926
			if (!empty($row['sent']) && $do_once)
927
			{
928
				$smcFunc['db_query']('', '
929
					UPDATE {db_prefix}log_notify
930
					SET sent = {int:is_not_sent}
931
					WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
932
						AND id_member = {int:current_member}',
933
					array(
934
						'current_board' => $board,
935
						'current_member' => $user_info['id'],
936
						'current_topic' => $topic,
937
						'is_not_sent' => 0,
938
					)
939
				);
940
				$do_once = false;
941
			}
942
		}
943
944
		// Have we recently cached the number of new topics in this board, and it's still a lot?
945
		if (isset($_REQUEST['topicseen']) && isset($_SESSION['topicseen_cache'][$board]) && $_SESSION['topicseen_cache'][$board] > 5)
946
			$_SESSION['topicseen_cache'][$board]--;
947
		// Mark board as seen if this is the only new topic.
948
		elseif (isset($_REQUEST['topicseen']))
949
		{
950
			// Use the mark read tables... and the last visit to figure out if this should be read or not.
951
			$request = $smcFunc['db_query']('', '
952
				SELECT COUNT(*)
953
				FROM {db_prefix}topics AS t
954
					LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = {int:current_board} AND lb.id_member = {int:current_member})
955
					LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
956
				WHERE t.id_board = {int:current_board}
957
					AND t.id_last_msg > COALESCE(lb.id_msg, 0)
958
					AND t.id_last_msg > COALESCE(lt.id_msg, 0)' . (empty($_SESSION['id_msg_last_visit']) ? '' : '
959
					AND t.id_last_msg > {int:id_msg_last_visit}'),
960
				array(
961
					'current_board' => $board,
962
					'current_member' => $user_info['id'],
963
					'id_msg_last_visit' => (int) $_SESSION['id_msg_last_visit'],
964
				)
965
			);
966
			list ($numNewTopics) = $smcFunc['db_fetch_row']($request);
967
			$smcFunc['db_free_result']($request);
968
969
			// If there're no real new topics in this board, mark the board as seen.
970
			if (empty($numNewTopics))
971
				$_REQUEST['boardseen'] = true;
972
			else
973
				$_SESSION['topicseen_cache'][$board] = $numNewTopics;
974
		}
975
		// Probably one less topic - maybe not, but even if we decrease this too fast it will only make us look more often.
976
		elseif (isset($_SESSION['topicseen_cache'][$board]))
977
			$_SESSION['topicseen_cache'][$board]--;
978
979
		// Mark board as seen if we came using last post link from BoardIndex. (or other places...)
980
		if (isset($_REQUEST['boardseen']))
981
		{
982
			$smcFunc['db_insert']('replace',
983
				'{db_prefix}log_boards',
984
				array('id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'),
985
				array($modSettings['maxMsgID'], $user_info['id'], $board),
986
				array('id_member', 'id_board')
987
			);
988
		}
989
	}
990
991
	// Get notification preferences
992
	$context['topicinfo']['notify_prefs'] = array();
993
	if (!empty($user_info['id']))
994
	{
995
		require_once($sourcedir . '/Subs-Notify.php');
996
		$prefs = getNotifyPrefs($user_info['id'], array('topic_notify', 'topic_notify_' . $context['current_topic']), true);
997
		$pref = !empty($prefs[$user_info['id']]) && $context['is_marked_notify'] ? $prefs[$user_info['id']] : array();
998
		$context['topicinfo']['notify_prefs'] = array(
999
			'is_custom' => isset($pref['topic_notify_' . $topic]),
1000
			'pref' => isset($pref['topic_notify_' . $context['current_topic']]) ? $pref['topic_notify_' . $context['current_topic']] : (!empty($pref['topic_notify']) ? $pref['topic_notify'] : 0),
1001
		);
1002
	}
1003
1004
	$context['topic_notification'] = !empty($user_info['id']) ? $context['topicinfo']['notify_prefs'] : array();
1005
	// 0 => unwatched, 1 => normal, 2 => receive alerts, 3 => receive emails
1006
	$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;
1007
1008
	$context['loaded_attachments'] = array();
1009
1010
	// If there _are_ messages here... (probably an error otherwise :!)
1011
	if (!empty($messages))
1012
	{
1013
		// Fetch attachments.
1014
		if (!empty($modSettings['attachmentEnable']) && allowedTo('view_attachments'))
1015
		{
1016
			$request = $smcFunc['db_query']('', '
1017
				SELECT
1018
					a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, COALESCE(a.size, 0) AS filesize, a.downloads, a.approved,
1019
					a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
1020
					COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
1021
				FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
1022
					LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
1023
				WHERE a.id_msg IN ({array_int:message_list})
1024
					AND a.attachment_type = {int:attachment_type}',
1025
				array(
1026
					'message_list' => $messages,
1027
					'attachment_type' => 0,
1028
					'is_approved' => 1,
1029
				)
1030
			);
1031
			$temp = array();
1032
			while ($row = $smcFunc['db_fetch_assoc']($request))
1033
			{
1034 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']))
1035
					continue;
1036
1037
				$temp[$row['id_attach']] = $row;
1038
				$temp[$row['id_attach']]['topic'] = $topic;
1039
				$temp[$row['id_attach']]['board'] = $board;
1040
1041
				if (!isset($context['loaded_attachments'][$row['id_msg']]))
1042
					$context['loaded_attachments'][$row['id_msg']] = array();
1043
			}
1044
			$smcFunc['db_free_result']($request);
1045
1046
			// This is better than sorting it with the query...
1047
			ksort($temp);
1048
1049
			foreach ($temp as $row)
1050
				$context['loaded_attachments'][$row['id_msg']][] = $row;
1051
		}
1052
1053
		$msg_parameters = array(
1054
			'message_list' => $messages,
1055
			'new_from' => $context['topicinfo']['new_from'],
1056
		);
1057
		$msg_selects = array();
1058
		$msg_tables = array();
1059
		call_integration_hook('integrate_query_message', array(&$msg_selects, &$msg_tables, &$msg_parameters));
1060
1061
		// What?  It's not like it *couldn't* be only guests in this topic...
1062
		loadMemberData($posters);
1063
		$messages_request = $smcFunc['db_query']('', '
1064
			SELECT
1065
				id_msg, icon, subject, poster_time, poster_ip, id_member, modified_time, modified_name, modified_reason, body,
1066
				smileys_enabled, poster_name, poster_email, approved, likes,
1067
				id_msg_modified < {int:new_from} AS is_read
1068
				' . (!empty($msg_selects) ? (', '. implode(', ', $msg_selects)) : '') . '
1069
			FROM {db_prefix}messages
1070
				' . (!empty($msg_tables) ? implode("\n\t", $msg_tables) : '') . '
1071
			WHERE id_msg IN ({array_int:message_list})
1072
			ORDER BY id_msg' . (empty($options['view_newest_first']) ? '' : ' DESC'),
1073
			$msg_parameters
1074
		);
1075
1076
		// And the likes
1077
		if (!empty($modSettings['enable_likes']))
1078
			$context['my_likes'] = $context['user']['is_guest'] ? array() : prepareLikesContext($topic);
1079
1080
		// Go to the last message if the given time is beyond the time of the last message.
1081 View Code Duplication
		if (isset($context['start_from']) && $context['start_from'] >= $context['topicinfo']['num_replies'])
1082
			$context['start_from'] = $context['topicinfo']['num_replies'];
1083
1084
		// Since the anchor information is needed on the top of the page we load these variables beforehand.
1085
		$context['first_message'] = isset($messages[$firstIndex]) ? $messages[$firstIndex] : $messages[0];
1086
		if (empty($options['view_newest_first']))
1087
			$context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $context['start_from'];
1088 View Code Duplication
		else
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1089
			$context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $context['topicinfo']['num_replies'] - $context['start_from'];
1090
	}
1091
	else
1092
	{
1093
		$messages_request = false;
1094
		$context['first_message'] = 0;
1095
		$context['first_new_message'] = false;
1096
1097
		$context['likes'] = array();
1098
	}
1099
1100
	$context['jump_to'] = array(
1101
		'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
1102
		'board_name' => $smcFunc['htmlspecialchars'](strtr(strip_tags($board_info['name']), array('&amp;' => '&'))),
1103
		'child_level' => $board_info['child_level'],
1104
	);
1105
1106
	// Set the callback.  (do you REALIZE how much memory all the messages would take?!?)
1107
	// This will be called from the template.
1108
	$context['get_message'] = 'prepareDisplayContext';
1109
1110
	// Now set all the wonderful, wonderful permissions... like moderation ones...
1111
	$common_permissions = array(
1112
		'can_approve' => 'approve_posts',
1113
		'can_ban' => 'manage_bans',
1114
		'can_sticky' => 'make_sticky',
1115
		'can_merge' => 'merge_any',
1116
		'can_split' => 'split_any',
1117
		'calendar_post' => 'calendar_post',
1118
		'can_send_pm' => 'pm_send',
1119
		'can_report_moderator' => 'report_any',
1120
		'can_moderate_forum' => 'moderate_forum',
1121
		'can_issue_warning' => 'issue_warning',
1122
		'can_restore_topic' => 'move_any',
1123
		'can_restore_msg' => 'move_any',
1124
		'can_see_likes' => 'likes_view',
1125
		'can_like' => 'likes_like',
1126
	);
1127
	foreach ($common_permissions as $contextual => $perm)
1128
		$context[$contextual] = allowedTo($perm);
1129
1130
	// Permissions with _any/_own versions.  $context[YYY] => ZZZ_any/_own.
1131
	$anyown_permissions = array(
1132
		'can_move' => 'move',
1133
		'can_lock' => 'lock',
1134
		'can_delete' => 'remove',
1135
		'can_add_poll' => 'poll_add',
1136
		'can_remove_poll' => 'poll_remove',
1137
		'can_reply' => 'post_reply',
1138
		'can_reply_unapproved' => 'post_unapproved_replies',
1139
		'can_view_warning' => 'profile_warning',
1140
	);
1141
	foreach ($anyown_permissions as $contextual => $perm)
1142
		$context[$contextual] = allowedTo($perm . '_any') || ($context['user']['started'] && allowedTo($perm . '_own'));
1143
1144
	if (!$user_info['is_admin'] && !$modSettings['topic_move_any'])
1145
	{
1146
		// We'll use this in a minute
1147
		$boards_allowed = array_diff(boardsAllowedTo('post_new'), array($board));
1148
1149
		/* You can't move this unless you have permission
1150
			to start new topics on at least one other board */
1151
		$context['can_move'] &= count($boards_allowed) > 1;
1152
	}
1153
1154
	// If a topic is locked, you can't remove it unless it's yours and you locked it or you can lock_any
1155
	if ($context['topicinfo']['locked'])
1156
	{
1157
		$context['can_delete'] &= (($context['topicinfo']['locked'] == 1 && $context['user']['started']) || allowedTo('lock_any'));
1158
	}
1159
1160
	// Cleanup all the permissions with extra stuff...
1161
	$context['can_mark_notify'] = !$context['user']['is_guest'];
1162
	$context['calendar_post'] &= !empty($modSettings['cal_enabled']);
1163
	$context['can_add_poll'] &= $modSettings['pollMode'] == '1' && $context['topicinfo']['id_poll'] <= 0;
1164
	$context['can_remove_poll'] &= $modSettings['pollMode'] == '1' && $context['topicinfo']['id_poll'] > 0;
1165
	$context['can_reply'] &= empty($context['topicinfo']['locked']) || allowedTo('moderate_board');
1166
	$context['can_reply_unapproved'] &= $modSettings['postmod_active'] && (empty($context['topicinfo']['locked']) || allowedTo('moderate_board'));
1167
	$context['can_issue_warning'] &= $modSettings['warning_settings'][0] == 1;
1168
	// Handle approval flags...
1169
	$context['can_reply_approved'] = $context['can_reply'];
1170
	$context['can_reply'] |= $context['can_reply_unapproved'];
1171
	$context['can_quote'] = $context['can_reply'] && (empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])));
1172
	$context['can_mark_unread'] = !$user_info['is_guest'];
1173
	$context['can_unwatch'] = !$user_info['is_guest'];
1174
	$context['can_set_notify'] = !$user_info['is_guest'];
1175
1176
	$context['can_print'] = empty($modSettings['disable_print_topic']);
1177
1178
	// Start this off for quick moderation - it will be or'd for each post.
1179
	$context['can_remove_post'] = allowedTo('delete_any') || (allowedTo('delete_replies') && $context['user']['started']);
1180
1181
	// Can restore topic?  That's if the topic is in the recycle board and has a previous restore state.
1182
	$context['can_restore_topic'] &= !empty($board_info['recycle']) && !empty($context['topicinfo']['id_previous_board']);
1183
	$context['can_restore_msg'] &= !empty($board_info['recycle']) && !empty($context['topicinfo']['id_previous_topic']);
1184
1185
	// Check if the draft functions are enabled and that they have permission to use them (for quick reply.)
1186
	$context['drafts_save'] = !empty($modSettings['drafts_post_enabled']) && allowedTo('post_draft') && $context['can_reply'];
1187
	$context['drafts_autosave'] = !empty($context['drafts_save']) && !empty($modSettings['drafts_autosave_enabled']);
1188
	if (!empty($context['drafts_save']))
1189
		loadLanguage('Drafts');
1190
1191
	// When was the last time this topic was replied to?  Should we warn them about it?
1192
	if (!empty($modSettings['oldTopicDays']) && ($context['can_reply'] || $context['can_reply_unapproved']) && empty($context['topicinfo']['is_sticky']))
1193
	{
1194
		$request = $smcFunc['db_query']('', '
1195
			SELECT poster_time
1196
			FROM {db_prefix}messages
1197
			WHERE id_msg = {int:id_last_msg}
1198
			LIMIT 1',
1199
			array(
1200
				'id_last_msg' => $context['topicinfo']['id_last_msg'],
1201
			)
1202
		);
1203
1204
		list ($lastPostTime) = $smcFunc['db_fetch_row']($request);
1205
		$smcFunc['db_free_result']($request);
1206
1207
		$context['oldTopicError'] = $lastPostTime + $modSettings['oldTopicDays'] * 86400 < time();
1208
	}
1209
1210
	// You can't link an existing topic to the calendar unless you can modify the first post...
1211
	$context['calendar_post'] &= allowedTo('modify_any') || (allowedTo('modify_own') && $context['user']['started']);
1212
1213
	// Load up the "double post" sequencing magic.
1214
	checkSubmitOnce('register');
1215
	$context['name'] = isset($_SESSION['guest_name']) ? $_SESSION['guest_name'] : '';
1216
	$context['email'] = isset($_SESSION['guest_email']) ? $_SESSION['guest_email'] : '';
1217
	// Needed for the editor and message icons.
1218
	require_once($sourcedir . '/Subs-Editor.php');
1219
1220
	// Now create the editor.
1221
	$editorOptions = array(
1222
		'id' => 'quickReply',
1223
		'value' => '',
1224
		'disable_smiley_box' => empty($options['use_editor_quick_reply']),
1225
		'labels' => array(
1226
			'post_button' => $txt['post'],
1227
		),
1228
		// add height and width for the editor
1229
		'height' => '250px',
1230
		'width' => '100%',
1231
		// We do HTML preview here.
1232
		'preview_type' => 1,
1233
		// This is required
1234
		'required' => true,
1235
	);
1236
	create_control_richedit($editorOptions);
1237
1238
	// Store the ID.
1239
	$context['post_box_name'] = $editorOptions['id'];
1240
1241
	// Set a flag so the sub template knows what to do...
1242
	$context['show_bbc'] = !empty($options['use_editor_quick_reply']);
1243
	$modSettings['disable_wysiwyg'] = !empty($options['use_editor_quick_reply']);
1244
	$context['attached'] = '';
1245
	$context['make_poll'] = isset($_REQUEST['poll']);
1246
1247
	// Message icons - customized icons are off?
1248
	$context['icons'] = getMessageIcons($board);
1249
1250 View Code Duplication
	if (!empty($context['icons']))
1251
		$context['icons'][count($context['icons']) - 1]['is_last'] = true;
1252
1253
	// Build the normal button array.
1254
	$context['normal_buttons'] = array();
1255
1256
	if ($context['can_reply'])
1257
		$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);
1258
1259 View Code Duplication
	if ($context['can_add_poll'])
1260
		$context['normal_buttons']['add_poll'] = array('text' => 'add_poll', 'image' => 'add_poll.png', 'url' => $scripturl . '?action=editpoll;add;topic=' . $context['current_topic'] . '.' . $context['start']);
1261
1262 View Code Duplication
	if ($context['can_mark_unread'])
1263
		$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']);
1264
1265
	if ($context['can_print'])
1266
		$context['normal_buttons']['print'] = array('text' => 'print', 'image' => 'print.png', 'custom' => 'rel="nofollow"', 'url' => $scripturl . '?action=printpage;topic=' . $context['current_topic'] . '.0');
1267
1268
	if ($context['can_set_notify'])
1269
		$context['normal_buttons']['notify'] = array(
1270
			'text' => 'notify_topic_' . $context['topic_notification_mode'],
1271
			'sub_buttons' => array(
1272
				array(
1273
					'test' => 'can_unwatch',
1274
					'text' => 'notify_topic_0',
1275
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=0;' . $context['session_var'] . '=' . $context['session_id'],
1276
				),
1277
				array(
1278
					'text' => 'notify_topic_1',
1279
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=1;' . $context['session_var'] . '=' . $context['session_id'],
1280
				),
1281
				array(
1282
					'text' => 'notify_topic_2',
1283
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=2;' . $context['session_var'] . '=' . $context['session_id'],
1284
				),
1285
				array(
1286
					'text' => 'notify_topic_3',
1287
					'url' => $scripturl . '?action=notifytopic;topic=' . $context['current_topic'] . ';mode=3;' . $context['session_var'] . '=' . $context['session_id'],
1288
				),
1289
			),
1290
		);
1291
1292
	// Build the mod button array
1293
	$context['mod_buttons'] = array();
1294
1295 View Code Duplication
	if ($context['can_move'])
1296
		$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');
1297
1298 View Code Duplication
	if ($context['can_delete'])
1299
		$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']);
1300
1301 View Code Duplication
	if ($context['can_lock'])
1302
		$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']);
1303
1304 View Code Duplication
	if ($context['can_sticky'])
1305
		$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']);
1306
1307 View Code Duplication
	if ($context['can_merge'])
1308
		$context['mod_buttons']['merge'] = array('text' => 'merge', 'image' => 'merge.png', 'url' => $scripturl . '?action=mergetopics;board=' . $context['current_board'] . '.0;from=' . $context['current_topic']);
1309
1310 View Code Duplication
	if ($context['calendar_post'])
1311
		$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');
1312
1313
	// Restore topic. eh?  No monkey business.
1314 View Code Duplication
	if ($context['can_restore_topic'])
1315
		$context['mod_buttons']['restore_topic'] = array('text' => 'restore_topic', 'image' => '', 'url' => $scripturl . '?action=restoretopic;topics=' . $context['current_topic'] . ';' . $context['session_var'] . '=' . $context['session_id']);
1316
1317
	// Show a message in case a recently posted message became unapproved.
1318
	$context['becomesUnapproved'] = !empty($_SESSION['becomesUnapproved']) ? true : false;
1319
1320
	// Don't want to show this forever...
1321
	if ($context['becomesUnapproved'])
1322
		unset($_SESSION['becomesUnapproved']);
1323
1324
	// Allow adding new mod buttons easily.
1325
	// Note: $context['normal_buttons'] and $context['mod_buttons'] are added for backward compatibility with 2.0, but are deprecated and should not be used
1326
	call_integration_hook('integrate_display_buttons', array(&$context['normal_buttons']));
1327
	// Note: integrate_mod_buttons is no more necessary and deprecated, but is kept for backward compatibility with 2.0
1328
	call_integration_hook('integrate_mod_buttons', array(&$context['mod_buttons']));
1329
1330
	// Load the drafts js file
1331
	if ($context['drafts_autosave'])
1332
		loadJavaScriptFile('drafts.js', array('defer' => false), 'smf_drafts');
1333
1334
	// Spellcheck
1335
	if ($context['show_spellchecking'])
1336
		loadJavaScriptFile('spellcheck.js', array('defer' => false), 'smf_spellcheck');
1337
1338
	// topic.js
1339
	loadJavaScriptFile('topic.js', array('defer' => false), 'smf_topic');
1340
1341
	// quotedText.js
1342
	loadJavaScriptFile('quotedText.js', array('defer' => true), 'smf_quotedText');
1343
1344
	// Mentions
1345 View Code Duplication
	if (!empty($modSettings['enable_mentions']) && allowedTo('mention'))
1346
	{
1347
		loadJavaScriptFile('jquery.atwho.min.js', array('defer' => true), 'smf_atwho');
1348
		loadJavaScriptFile('jquery.caret.min.js', array('defer' => true), 'smf_caret');
1349
		loadJavaScriptFile('mentions.js', array('defer' => true), 'smf_mentions');
1350
	}
1351
}
1352
1353
/**
1354
 * Callback for the message display.
1355
 * It actually gets and prepares the message context.
1356
 * This function will start over from the beginning if reset is set to true, which is
1357
 * useful for showing an index before or after the posts.
1358
 *
1359
 * @param bool $reset Whether or not to reset the db seek pointer
1360
 * @return array A large array of contextual data for the posts
1361
 */
1362
function prepareDisplayContext($reset = false)
1363
{
1364
	global $settings, $txt, $modSettings, $scripturl, $options, $user_info, $smcFunc;
1365
	global $memberContext, $context, $messages_request, $topic, $board_info, $sourcedir;
1366
1367
	static $counter = null;
1368
1369
	// If the query returned false, bail.
1370
	if ($messages_request == false)
1371
		return false;
1372
1373
	// Remember which message this is.  (ie. reply #83)
1374
	if ($counter === null || $reset)
1375
		$counter = empty($options['view_newest_first']) ? $context['start'] : $context['total_visible_posts'] - $context['start'];
1376
1377
	// Start from the beginning...
1378
	if ($reset)
1379
		return @$smcFunc['db_data_seek']($messages_request, 0);
1380
1381
	// Attempt to get the next message.
1382
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1383
	if (!$message)
1384
	{
1385
		$smcFunc['db_free_result']($messages_request);
1386
		return false;
1387
	}
1388
1389
	// $context['icon_sources'] says where each icon should come from - here we set up the ones which will always exist!
1390
	if (empty($context['icon_sources']))
1391
	{
1392
		$context['icon_sources'] = array();
1393
		foreach ($context['stable_icons'] as $icon)
1394
			$context['icon_sources'][$icon] = 'images_url';
1395
	}
1396
1397
	// Message Icon Management... check the images exist.
1398
	if (empty($modSettings['messageIconChecks_disable']))
1399
	{
1400
		// If the current icon isn't known, then we need to do something...
1401 View Code Duplication
		if (!isset($context['icon_sources'][$message['icon']]))
1402
			$context['icon_sources'][$message['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['icon'] . '.png') ? 'images_url' : 'default_images_url';
1403
	}
1404 View Code Duplication
	elseif (!isset($context['icon_sources'][$message['icon']]))
1405
		$context['icon_sources'][$message['icon']] = 'images_url';
1406
1407
	// If you're a lazy bum, you probably didn't give a subject...
1408
	$message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject'];
1409
1410
	// Are you allowed to remove at least a single reply?
1411
	$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'];
1412
1413
	// If the topic is locked, you might not be able to delete the post...
1414
	if ($context['is_locked'])
1415
	{
1416
		$context['can_remove_post'] &= ($context['user']['started'] && $context['is_locked'] == 1) || allowedTo('lock_any');
1417
	}
1418
1419
	// If it couldn't load, or the user was a guest.... someday may be done with a guest table.
1420
	if (!loadMemberContext($message['id_member'], true))
1421
	{
1422
		// Notice this information isn't used anywhere else....
1423
		$memberContext[$message['id_member']]['name'] = $message['poster_name'];
1424
		$memberContext[$message['id_member']]['id'] = 0;
1425
		$memberContext[$message['id_member']]['group'] = $txt['guest_title'];
1426
		$memberContext[$message['id_member']]['link'] = $message['poster_name'];
1427
		$memberContext[$message['id_member']]['email'] = $message['poster_email'];
1428
		$memberContext[$message['id_member']]['show_email'] = allowedTo('moderate_forum');
1429
		$memberContext[$message['id_member']]['is_guest'] = true;
1430
	}
1431
	else
1432
	{
1433
		// Define this here to make things a bit more readable
1434
		$can_view_warning = $context['user']['can_mod'] || allowedTo('view_warning_any') || ($message['id_member'] == $user_info['id'] && allowedTo('view_warning_own'));
1435
1436
		$memberContext[$message['id_member']]['can_view_profile'] = allowedTo('profile_view') || ($message['id_member'] == $user_info['id'] && !$user_info['is_guest']);
1437
		$memberContext[$message['id_member']]['is_topic_starter'] = $message['id_member'] == $context['topic_starter_id'];
1438
		$memberContext[$message['id_member']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member']]['warning_status'] && $can_view_warning;
1439
		// Show the email if it's your post...
1440
		$memberContext[$message['id_member']]['show_email'] |= ($message['id_member'] == $user_info['id']);
1441
	}
1442
1443
	$memberContext[$message['id_member']]['ip'] = inet_dtop($message['poster_ip']);
1444
	$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']);
1445
1446
	// Do the censor thang.
1447
	censorText($message['body']);
1448
	censorText($message['subject']);
1449
1450
	// Run BBC interpreter on the message.
1451
	$message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1452
1453
	// If it's in the recycle bin we need to override whatever icon we did have.
1454
	if (!empty($board_info['recycle']))
1455
		$message['icon'] = 'recycled';
1456
1457
	require_once($sourcedir . '/Subs-Attachments.php');
1458
1459
	// Compose the memory eat- I mean message array.
1460
	$output = array(
1461
		'attachment' => loadAttachmentContext($message['id_msg'], $context['loaded_attachments']),
1462
		'id' => $message['id_msg'],
1463
		'href' => $scripturl . '?topic=' . $topic . '.msg' . $message['id_msg'] . '#msg' . $message['id_msg'],
1464
		'link' => '<a href="' . $scripturl . '?msg=' . $message['id_msg'] . '" rel="nofollow">' . $message['subject'] . '</a>',
1465
		'member' => &$memberContext[$message['id_member']],
1466
		'icon' => $message['icon'],
1467
		'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.png',
1468
		'subject' => $message['subject'],
1469
		'time' => timeformat($message['poster_time']),
1470
		'timestamp' => forum_time(true, $message['poster_time']),
1471
		'counter' => $counter,
1472
		'modified' => array(
1473
			'time' => timeformat($message['modified_time']),
1474
			'timestamp' => forum_time(true, $message['modified_time']),
1475
			'name' => $message['modified_name'],
1476
			'reason' => $message['modified_reason']
1477
		),
1478
		'body' => $message['body'],
1479
		'new' => empty($message['is_read']),
1480
		'approved' => $message['approved'],
1481
		'first_new' => isset($context['start_from']) && $context['start_from'] == $counter,
1482
		'is_ignored' => !empty($modSettings['enable_buddylist']) && !empty($options['posts_apply_ignore_list']) && in_array($message['id_member'], $context['user']['ignoreusers']),
1483
		'can_approve' => !$message['approved'] && $context['can_approve'],
1484
		'can_unapprove' => !empty($modSettings['postmod_active']) && $context['can_approve'] && $message['approved'],
1485
		'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()))),
1486
		'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())),
1487
		'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member'] == $user_info['id'] && !empty($user_info['id'])),
1488
		'css_class' => $message['approved'] ? 'windowbg' : 'approvebg',
1489
	);
1490
1491
	// Does the file contains any attachments? if so, change the icon.
1492
	if (!empty($output['attachment']))
1493
	{
1494
		$output['icon'] = 'clip';
1495
		$output['icon_url'] = $settings[$context['icon_sources'][$output['icon']]] . '/post/' . $output['icon'] . '.png';
1496
	}
1497
1498
	// Are likes enable?
1499 View Code Duplication
	if (!empty($modSettings['enable_likes']))
1500
		$output['likes'] = array(
1501
			'count' => $message['likes'],
1502
			'you' => in_array($message['id_msg'], $context['my_likes']),
1503
			'can_like' => !$context['user']['is_guest'] && $message['id_member'] != $context['user']['id'] && !empty($context['can_like']),
1504
		);
1505
1506
	// Is this user the message author?
1507
	$output['is_message_author'] = $message['id_member'] == $user_info['id'];
1508 View Code Duplication
	if (!empty($output['modified']['name']))
1509
		$output['modified']['last_edit_text'] = sprintf($txt['last_edit_by'], $output['modified']['time'], $output['modified']['name']);
1510
1511
	// Did they give a reason for editing?
1512 View Code Duplication
	if (!empty($output['modified']['name']) && !empty($output['modified']['reason']))
1513
		$output['modified']['last_edit_text'] .= '&nbsp;' . sprintf($txt['last_edit_reason'], $output['modified']['reason']);
1514
1515
	// Any custom profile fields?
1516
	if (!empty($memberContext[$message['id_member']]['custom_fields']))
1517
		foreach ($memberContext[$message['id_member']]['custom_fields'] as $custom)
1518
			$output['custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
1519
1520
	if (empty($options['view_newest_first']))
1521
		$counter++;
1522
1523
	else
1524
		$counter--;
1525
1526
	call_integration_hook('integrate_prepare_display_context', array(&$output, &$message, $counter));
1527
1528
	return $output;
1529
}
1530
1531
/**
1532
 * Downloads an attachment, and increments the download count.
1533
 * It requires the view_attachments permission.
1534
 * It disables the session parser, and clears any previous output.
1535
 * It depends on the attachmentUploadDir setting being correct.
1536
 * It is accessed via the query string ?action=dlattach.
1537
 * Views to attachments do not increase hits and are not logged in the "Who's Online" log.
1538
 * Legacy code, all attachments are now handled by ShowAttachments.php
1539
 */
1540
function Download()
1541
{
1542
	global $txt, $modSettings, $user_info, $context, $topic, $smcFunc;
1543
1544
	// Some defaults that we need.
1545
	$context['character_set'] = empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'ISO-8859-1' : $txt['lang_character_set']) : $modSettings['global_character_set'];
1546
	$context['utf8'] = $context['character_set'] === 'UTF-8';
1547
	$context['no_last_modified'] = true;
1548
1549
	// Prevent a preview image from being displayed twice.
1550
	if (isset($_GET['action']) && $_GET['action'] == 'dlattach' && isset($_GET['type']) && ($_GET['type'] == 'avatar' || $_GET['type'] == 'preview'))
1551
		return;
1552
1553
	// Make sure some attachment was requested!
1554
	if (!isset($_REQUEST['attach']) && !isset($_REQUEST['id']))
1555
		fatal_lang_error('no_access', false);
0 ignored issues
show
false is of type boolean, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1556
1557
	$_REQUEST['attach'] = isset($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : (int) $_REQUEST['id'];
1558
1559
	// Do we have a hook wanting to use our attachment system? We use $attachRequest to prevent accidental usage of $request.
1560
	$attachRequest = null;
1561
	call_integration_hook('integrate_download_request', array(&$attachRequest));
1562
	if (!is_null($attachRequest) && $smcFunc['db_is_resource']($attachRequest))
1563
		$request = $attachRequest;
1564
	else
1565
	{
1566
		// This checks only the current board for $board/$topic's permissions.
1567
		isAllowedTo('view_attachments');
1568
1569
		// Make sure this attachment is on this board.
1570
		// @todo: We must verify that $topic is the attachment's topic, or else the permission check above is broken.
1571
		$request = $smcFunc['db_query']('', '
1572
			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
1573
			FROM {db_prefix}attachments AS a
1574
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
1575
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1576
			WHERE a.id_attach = {int:attach}
1577
			LIMIT 1',
1578
			array(
1579
				'attach' => $_REQUEST['attach'],
1580
				'current_topic' => $topic,
1581
			)
1582
		);
1583
	}
1584
1585
	if ($smcFunc['db_num_rows']($request) == 0)
1586
		fatal_lang_error('no_access', false);
0 ignored issues
show
false is of type boolean, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1587
1588
	list ($id_folder, $real_filename, $file_hash, $file_ext, $id_attach, $attachment_type, $mime_type, $is_approved, $id_member) = $smcFunc['db_fetch_row']($request);
1589
	$smcFunc['db_free_result']($request);
1590
1591
	// If it isn't yet approved, do they have permission to view it?
1592
	if (!$is_approved && ($id_member == 0 || $user_info['id'] != $id_member) && ($attachment_type == 0 || $attachment_type == 3))
1593
		isAllowedTo('approve_posts');
1594
1595
	// Update the download counter (unless it's a thumbnail).
1596
	if ($attachment_type != 3)
1597
		$smcFunc['db_query']('attach_download_increase', '
1598
			UPDATE LOW_PRIORITY {db_prefix}attachments
1599
			SET downloads = downloads + 1
1600
			WHERE id_attach = {int:id_attach}',
1601
			array(
1602
				'id_attach' => $id_attach,
1603
			)
1604
		);
1605
1606
	$filename = getAttachmentFilename($real_filename, $_REQUEST['attach'], $id_folder, false, $file_hash);
1607
1608
	// This is done to clear any output that was made before now.
1609
	ob_end_clean();
1610
	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')))
1611
		@ob_start('ob_gzhandler');
1612
1613
	else
1614
	{
1615
		ob_start();
1616
		header('Content-Encoding: none');
1617
	}
1618
1619
	// No point in a nicer message, because this is supposed to be an attachment anyway...
1620 View Code Duplication
	if (!file_exists($filename))
1621
	{
1622
		header((preg_match('~HTTP/1\.[01]~i', $_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0') . ' 404 Not Found');
1623
		header('Content-Type: text/plain; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
1624
1625
		// We need to die like this *before* we send any anti-caching headers as below.
1626
		die('File not found.');
1627
	}
1628
1629
	// If it hasn't been modified since the last time this attachment was retrieved, there's no need to display it again.
1630 View Code Duplication
	if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
1631
	{
1632
		list($modified_since) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
1633
		if (strtotime($modified_since) >= filemtime($filename))
1634
		{
1635
			ob_end_clean();
1636
1637
			// Answer the question - no, it hasn't been modified ;).
1638
			header('HTTP/1.1 304 Not Modified');
1639
			exit;
1640
		}
1641
	}
1642
1643
	// Check whether the ETag was sent back, and cache based on that...
1644
	$eTag = '"' . substr($_REQUEST['attach'] . $real_filename . filemtime($filename), 0, 64) . '"';
1645 View Code Duplication
	if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $eTag) !== false)
1646
	{
1647
		ob_end_clean();
1648
1649
		header('HTTP/1.1 304 Not Modified');
1650
		exit;
1651
	}
1652
1653
	// Send the attachment headers.
1654
	header('Pragma: ');
1655
1656
	if (!isBrowser('gecko'))
1657
		header('Content-Transfer-Encoding: binary');
1658
1659
	header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT');
1660
	header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filename)) . ' GMT');
1661
	header('Accept-Ranges: bytes');
1662
	header('Connection: close');
1663
	header('ETag: ' . $eTag);
1664
1665
	// Make sure the mime type warrants an inline display.
1666
	if (isset($_REQUEST['image']) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0)
1667
		unset($_REQUEST['image']);
1668
1669
	// Does this have a mime type?
1670
	elseif (!empty($mime_type) && (isset($_REQUEST['image']) || !in_array($file_ext, array('jpg', 'gif', 'jpeg', 'x-ms-bmp', 'png', 'psd', 'tiff', 'iff'))))
1671
		header('Content-Type: ' . strtr($mime_type, array('image/bmp' => 'image/x-ms-bmp')));
1672
1673 View Code Duplication
	else
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1674
	{
1675
		header('Content-Type: ' . (isBrowser('ie') || isBrowser('opera') ? 'application/octetstream' : 'application/octet-stream'));
1676
		if (isset($_REQUEST['image']))
1677
			unset($_REQUEST['image']);
1678
	}
1679
1680
	// Convert the file to UTF-8, cuz most browsers dig that.
1681
	$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);
1682
	$disposition = !isset($_REQUEST['image']) ? 'attachment' : 'inline';
1683
1684
	// Different browsers like different standards...
1685 View Code Duplication
	if (isBrowser('firefox'))
1686
		header('Content-Disposition: ' . $disposition . '; filename*=UTF-8\'\'' . rawurlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name)));
1687
1688
	elseif (isBrowser('opera'))
1689
		header('Content-Disposition: ' . $disposition . '; filename="' . preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name) . '"');
1690
1691
	elseif (isBrowser('ie'))
1692
		header('Content-Disposition: ' . $disposition . '; filename="' . urlencode(preg_replace_callback('~&#(\d{3,8});~', 'fixchar__callback', $utf8name)) . '"');
1693
1694
	else
1695
		header('Content-Disposition: ' . $disposition . '; filename="' . $utf8name . '"');
1696
1697
	// If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE.
1698 View Code Duplication
	if (!isset($_REQUEST['image']) && in_array($file_ext, array('gif', 'jpg', 'bmp', 'png', 'jpeg', 'tiff')))
1699
		header('Cache-Control: no-cache');
1700
	else
1701
		header('Cache-Control: max-age=' . (525600 * 60) . ', private');
1702
1703
	header('Content-Length: ' . filesize($filename));
1704
1705
	// Try to buy some time...
1706
	@set_time_limit(600);
1707
1708
	// Recode line endings for text files, if enabled.
1709 View Code Duplication
	if (!empty($modSettings['attachmentRecodeLineEndings']) && !isset($_REQUEST['image']) && in_array($file_ext, array('txt', 'css', 'htm', 'html', 'php', 'xml')))
1710
	{
1711
		if (strpos($_SERVER['HTTP_USER_AGENT'], 'Windows') !== false)
1712
			$callback = function ($buffer)
1713
			{
1714
				return preg_replace('~[\r]?\n~', "\r\n", $buffer);
1715
			};
1716
		elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') !== false)
1717
			$callback = function ($buffer)
1718
			{
1719
				return preg_replace('~[\r]?\n~', "\r", $buffer);
1720
			};
1721
		else
1722
			$callback = function ($buffer)
1723
			{
1724
				return preg_replace('~[\r]?\n~', "\n", $buffer);
1725
			};
1726
	}
1727
1728
	// Since we don't do output compression for files this large...
1729
	if (filesize($filename) > 4194304)
1730
	{
1731
		// Forcibly end any output buffering going on.
1732
		while (@ob_get_level() > 0)
1733
			@ob_end_clean();
1734
1735
		$fp = fopen($filename, 'rb');
1736
		while (!feof($fp))
1737
		{
1738
			if (isset($callback))
1739
				echo $callback(fread($fp, 8192));
1740
			else
1741
				echo fread($fp, 8192);
1742
			flush();
1743
		}
1744
		fclose($fp);
1745
	}
1746
	// 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.
1747
	elseif (isset($callback) || @readfile($filename) === null)
1748
		echo isset($callback) ? $callback(file_get_contents($filename)) : file_get_contents($filename);
1749
1750
	obExit(false);
1751
}
1752
1753
/**
1754
 * A sort function for putting unapproved attachments first.
1755
 * @param array $a An array of info about one attachment
1756
 * @param array $b An array of info about a second attachment
1757
 * @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
1758
 */
1759
function approved_attach_sort($a, $b)
1760
{
1761
	if ($a['is_approved'] == $b['is_approved'])
1762
		return 0;
1763
1764
	return $a['is_approved'] > $b['is_approved'] ? -1 : 1;
1765
}
1766
1767
/**
1768
 * In-topic quick moderation.
1769
 */
1770
function QuickInTopicModeration()
1771
{
1772
	global $sourcedir, $topic, $board, $user_info, $smcFunc, $modSettings, $context;
1773
1774
	// Check the session = get or post.
1775
	checkSession('request');
1776
1777
	require_once($sourcedir . '/RemoveTopic.php');
1778
1779
	if (empty($_REQUEST['msgs']))
1780
		redirectexit('topic=' . $topic . '.' . $_REQUEST['start']);
1781
1782
	$messages = array();
1783
	foreach ($_REQUEST['msgs'] as $dummy)
1784
		$messages[] = (int) $dummy;
1785
1786
	// We are restoring messages. We handle this in another place.
1787 View Code Duplication
	if (isset($_REQUEST['restore_selected']))
1788
		redirectexit('action=restoretopic;msgs=' . implode(',', $messages) . ';' . $context['session_var'] . '=' . $context['session_id']);
1789
	if (isset($_REQUEST['split_selection']))
1790
	{
1791
		$request = $smcFunc['db_query']('', '
1792
			SELECT subject
1793
			FROM {db_prefix}messages
1794
			WHERE id_msg = {int:message}
1795
			LIMIT 1',
1796
			array(
1797
				'message' => min($messages),
1798
			)
1799
		);
1800
		list($subname) = $smcFunc['db_fetch_row']($request);
1801
		$smcFunc['db_free_result']($request);
1802
		$_SESSION['split_selection'][$topic] = $messages;
1803
		redirectexit('action=splittopics;sa=selectTopics;topic=' . $topic . '.0;subname_enc=' .urlencode($subname) . ';' . $context['session_var'] . '=' . $context['session_id']);
1804
	}
1805
1806
	// Allowed to delete any message?
1807
	if (allowedTo('delete_any'))
1808
		$allowed_all = true;
1809
	// Allowed to delete replies to their messages?
1810 View Code Duplication
	elseif (allowedTo('delete_replies'))
1811
	{
1812
		$request = $smcFunc['db_query']('', '
1813
			SELECT id_member_started
1814
			FROM {db_prefix}topics
1815
			WHERE id_topic = {int:current_topic}
1816
			LIMIT 1',
1817
			array(
1818
				'current_topic' => $topic,
1819
			)
1820
		);
1821
		list ($starter) = $smcFunc['db_fetch_row']($request);
1822
		$smcFunc['db_free_result']($request);
1823
1824
		$allowed_all = $starter == $user_info['id'];
1825
	}
1826
	else
1827
		$allowed_all = false;
1828
1829
	// Make sure they're allowed to delete their own messages, if not any.
1830
	if (!$allowed_all)
1831
		isAllowedTo('delete_own');
1832
1833
	// Allowed to remove which messages?
1834
	$request = $smcFunc['db_query']('', '
1835
		SELECT id_msg, subject, id_member, poster_time
1836
		FROM {db_prefix}messages
1837
		WHERE id_msg IN ({array_int:message_list})
1838
			AND id_topic = {int:current_topic}' . (!$allowed_all ? '
1839
			AND id_member = {int:current_member}' : '') . '
1840
		LIMIT {int:limit}',
1841
		array(
1842
			'current_member' => $user_info['id'],
1843
			'current_topic' => $topic,
1844
			'message_list' => $messages,
1845
			'limit' => count($messages),
1846
		)
1847
	);
1848
	$messages = array();
1849
	while ($row = $smcFunc['db_fetch_assoc']($request))
1850
	{
1851
		if (!$allowed_all && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time())
1852
			continue;
1853
1854
		$messages[$row['id_msg']] = array($row['subject'], $row['id_member']);
1855
	}
1856
	$smcFunc['db_free_result']($request);
1857
1858
	// Get the first message in the topic - because you can't delete that!
1859
	$request = $smcFunc['db_query']('', '
1860
		SELECT id_first_msg, id_last_msg
1861
		FROM {db_prefix}topics
1862
		WHERE id_topic = {int:current_topic}
1863
		LIMIT 1',
1864
		array(
1865
			'current_topic' => $topic,
1866
		)
1867
	);
1868
	list ($first_message, $last_message) = $smcFunc['db_fetch_row']($request);
1869
	$smcFunc['db_free_result']($request);
1870
1871
	// Delete all the messages we know they can delete. ($messages)
1872
	foreach ($messages as $message => $info)
1873
	{
1874
		// Just skip the first message - if it's not the last.
1875
		if ($message == $first_message && $message != $last_message)
1876
			continue;
1877
		// If the first message is going then don't bother going back to the topic as we're effectively deleting it.
1878
		elseif ($message == $first_message)
1879
			$topicGone = true;
1880
1881
		removeMessage($message);
1882
1883
		// Log this moderation action ;).
1884 View Code Duplication
		if (allowedTo('delete_any') && (!allowedTo('delete_own') || $info[1] != $user_info['id']))
1885
			logAction('delete', array('topic' => $topic, 'subject' => $info[0], 'member' => $info[1], 'board' => $board));
1886
	}
1887
1888
	redirectexit(!empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start']);
1889
}
1890
1891
?>