comparison forum/Sources/Display.php @ 76:e3e11437ecea website

Add forum code
author Chris Cannam
date Sun, 07 Jul 2013 11:25:48 +0200
parents
children
comparison
equal deleted inserted replaced
75:72f59aa7e503 76:e3e11437ecea
1 <?php
2
3 /**
4 * Simple Machines Forum (SMF)
5 *
6 * @package SMF
7 * @author Simple Machines http://www.simplemachines.org
8 * @copyright 2011 Simple Machines
9 * @license http://www.simplemachines.org/about/smf/license.php BSD
10 *
11 * @version 2.0
12 */
13
14 if (!defined('SMF'))
15 die('Hacking attempt...');
16
17 /* This is perhaps the most important and probably most accessed files in all
18 of SMF. This file controls topic, message, and attachment display. It
19 does so with the following functions:
20
21 void Display()
22 - loads the posts in a topic up so they can be displayed.
23 - supports wireless, using wap/wap2/imode and the Wireless templates.
24 - uses the main sub template of the Display template.
25 - requires a topic, and can go to the previous or next topic from it.
26 - jumps to the correct post depending on a number/time/IS_MSG passed.
27 - depends on the messages_per_page, defaultMaxMessages and enableAllMessages settings.
28 - is accessed by ?topic=id_topic.START.
29
30 array prepareDisplayContext(bool reset = false)
31 - actually gets and prepares the message context.
32 - starts over from the beginning if reset is set to true, which is
33 useful for showing an index before or after the posts.
34
35 void Download()
36 - downloads an attachment or avatar, and increments the downloads.
37 - requires the view_attachments permission. (not for avatars!)
38 - disables the session parser, and clears any previous output.
39 - depends on the attachmentUploadDir setting being correct.
40 - is accessed via the query string ?action=dlattach.
41 - views to attachments and avatars do not increase hits and are not
42 logged in the "Who's Online" log.
43
44 array loadAttachmentContext(int id_msg)
45 - loads an attachment's contextual data including, most importantly,
46 its size if it is an image.
47 - expects the $attachments array to have been filled with the proper
48 attachment data, as Display() does.
49 - requires the view_attachments permission to calculate image size.
50 - attempts to keep the "aspect ratio" of the posted image in line,
51 even if it has to be resized by the max_image_width and
52 max_image_height settings.
53
54 int approved_attach_sort(array a, array b)
55 - a sort function for putting unapproved attachments first.
56
57 void QuickInTopicModeration()
58 - in-topic quick moderation.
59
60 */
61
62 // The central part of the board - topic display.
63 function Display()
64 {
65 global $scripturl, $txt, $modSettings, $context, $settings;
66 global $options, $sourcedir, $user_info, $board_info, $topic, $board;
67 global $attachments, $messages_request, $topicinfo, $language, $smcFunc;
68
69 // What are you gonna display if these are empty?!
70 if (empty($topic))
71 fatal_lang_error('no_board', false);
72
73 // Load the proper template and/or sub template.
74 if (WIRELESS)
75 $context['sub_template'] = WIRELESS_PROTOCOL . '_display';
76 else
77 loadTemplate('Display');
78
79 // Not only does a prefetch make things slower for the server, but it makes it impossible to know if they read it.
80 if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch')
81 {
82 ob_end_clean();
83 header('HTTP/1.1 403 Prefetch Forbidden');
84 die;
85 }
86
87 // How much are we sticking on each page?
88 $context['messages_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) && !WIRELESS ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
89
90 // Let's do some work on what to search index.
91 if (count($_GET) > 2)
92 foreach ($_GET as $k => $v)
93 {
94 if (!in_array($k, array('topic', 'board', 'start', session_name())))
95 $context['robot_no_index'] = true;
96 }
97
98 if (!empty($_REQUEST['start']) && (!is_numeric($_REQUEST['start']) || $_REQUEST['start'] % $context['messages_per_page'] != 0))
99 $context['robot_no_index'] = true;
100
101 // Find the previous or next topic. Make a fuss if there are no more.
102 if (isset($_REQUEST['prev_next']) && ($_REQUEST['prev_next'] == 'prev' || $_REQUEST['prev_next'] == 'next'))
103 {
104 // No use in calculating the next topic if there's only one.
105 if ($board_info['num_topics'] > 1)
106 {
107 // Just prepare some variables that are used in the query.
108 $gt_lt = $_REQUEST['prev_next'] == 'prev' ? '>' : '<';
109 $order = $_REQUEST['prev_next'] == 'prev' ? '' : ' DESC';
110
111 $request = $smcFunc['db_query']('', '
112 SELECT t2.id_topic
113 FROM {db_prefix}topics AS t
114 INNER JOIN {db_prefix}topics AS t2 ON (' . (empty($modSettings['enableStickyTopics']) ? '
115 t2.id_last_msg ' . $gt_lt . ' t.id_last_msg' : '
116 (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') . ')
117 WHERE t.id_topic = {int:current_topic}
118 AND t2.id_board = {int:current_board}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
119 AND (t2.approved = {int:is_approved} OR (t2.id_member_started != {int:id_member_started} AND t2.id_member_started = {int:current_member}))') . '
120 ORDER BY' . (empty($modSettings['enableStickyTopics']) ? '' : ' t2.is_sticky' . $order . ',') . ' t2.id_last_msg' . $order . '
121 LIMIT 1',
122 array(
123 'current_board' => $board,
124 'current_member' => $user_info['id'],
125 'current_topic' => $topic,
126 'is_approved' => 1,
127 'id_member_started' => 0,
128 )
129 );
130
131 // No more left.
132 if ($smcFunc['db_num_rows']($request) == 0)
133 {
134 $smcFunc['db_free_result']($request);
135
136 // Roll over - if we're going prev, get the last - otherwise the first.
137 $request = $smcFunc['db_query']('', '
138 SELECT id_topic
139 FROM {db_prefix}topics
140 WHERE id_board = {int:current_board}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
141 AND (approved = {int:is_approved} OR (id_member_started != {int:id_member_started} AND id_member_started = {int:current_member}))') . '
142 ORDER BY' . (empty($modSettings['enableStickyTopics']) ? '' : ' is_sticky' . $order . ',') . ' id_last_msg' . $order . '
143 LIMIT 1',
144 array(
145 'current_board' => $board,
146 'current_member' => $user_info['id'],
147 'is_approved' => 1,
148 'id_member_started' => 0,
149 )
150 );
151 }
152
153 // Now you can be sure $topic is the id_topic to view.
154 list ($topic) = $smcFunc['db_fetch_row']($request);
155 $smcFunc['db_free_result']($request);
156
157 $context['current_topic'] = $topic;
158 }
159
160 // Go to the newest message on this topic.
161 $_REQUEST['start'] = 'new';
162 }
163
164 // Add 1 to the number of views of this topic.
165 if (empty($_SESSION['last_read_topic']) || $_SESSION['last_read_topic'] != $topic)
166 {
167 $smcFunc['db_query']('', '
168 UPDATE {db_prefix}topics
169 SET num_views = num_views + 1
170 WHERE id_topic = {int:current_topic}',
171 array(
172 'current_topic' => $topic,
173 )
174 );
175
176 $_SESSION['last_read_topic'] = $topic;
177 }
178
179 // Get all the important topic info.
180 $request = $smcFunc['db_query']('', '
181 SELECT
182 t.num_replies, t.num_views, t.locked, ms.subject, t.is_sticky, t.id_poll,
183 t.id_member_started, t.id_first_msg, t.id_last_msg, t.approved, t.unapproved_posts,
184 ' . ($user_info['is_guest'] ? 't.id_last_msg + 1' : 'IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1') . ' AS new_from
185 ' . (!empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board ? ', id_previous_board, id_previous_topic' : '') . '
186 FROM {db_prefix}topics AS t
187 INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)' . ($user_info['is_guest'] ? '' : '
188 LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = {int:current_topic} AND lt.id_member = {int:current_member})
189 LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})') . '
190 WHERE t.id_topic = {int:current_topic}
191 LIMIT 1',
192 array(
193 'current_member' => $user_info['id'],
194 'current_topic' => $topic,
195 'current_board' => $board,
196 )
197 );
198 if ($smcFunc['db_num_rows']($request) == 0)
199 fatal_lang_error('not_a_topic', false);
200 $topicinfo = $smcFunc['db_fetch_assoc']($request);
201 $smcFunc['db_free_result']($request);
202
203 $context['real_num_replies'] = $context['num_replies'] = $topicinfo['num_replies'];
204 $context['topic_first_message'] = $topicinfo['id_first_msg'];
205 $context['topic_last_message'] = $topicinfo['id_last_msg'];
206
207 // Add up unapproved replies to get real number of replies...
208 if ($modSettings['postmod_active'] && allowedTo('approve_posts'))
209 $context['real_num_replies'] += $topicinfo['unapproved_posts'] - ($topicinfo['approved'] ? 0 : 1);
210
211 // If this topic has unapproved posts, we need to work out how many posts the user can see, for page indexing.
212 if ($modSettings['postmod_active'] && $topicinfo['unapproved_posts'] && !$user_info['is_guest'] && !allowedTo('approve_posts'))
213 {
214 $request = $smcFunc['db_query']('', '
215 SELECT COUNT(id_member) AS my_unapproved_posts
216 FROM {db_prefix}messages
217 WHERE id_topic = {int:current_topic}
218 AND id_member = {int:current_member}
219 AND approved = 0',
220 array(
221 'current_topic' => $topic,
222 'current_member' => $user_info['id'],
223 )
224 );
225 list ($myUnapprovedPosts) = $smcFunc['db_fetch_row']($request);
226 $smcFunc['db_free_result']($request);
227
228 $context['total_visible_posts'] = $context['num_replies'] + $myUnapprovedPosts + ($topicinfo['approved'] ? 1 : 0);
229 }
230 else
231 $context['total_visible_posts'] = $context['num_replies'] + $topicinfo['unapproved_posts'] + ($topicinfo['approved'] ? 1 : 0);
232
233 // When was the last time this topic was replied to? Should we warn them about it?
234 $request = $smcFunc['db_query']('', '
235 SELECT poster_time
236 FROM {db_prefix}messages
237 WHERE id_msg = {int:id_last_msg}
238 LIMIT 1',
239 array(
240 'id_last_msg' => $topicinfo['id_last_msg'],
241 )
242 );
243
244 list ($lastPostTime) = $smcFunc['db_fetch_row']($request);
245 $smcFunc['db_free_result']($request);
246
247 $context['oldTopicError'] = !empty($modSettings['oldTopicDays']) && $lastPostTime + $modSettings['oldTopicDays'] * 86400 < time() && empty($sticky);
248
249 // The start isn't a number; it's information about what to do, where to go.
250 if (!is_numeric($_REQUEST['start']))
251 {
252 // Redirect to the page and post with new messages, originally by Omar Bazavilvazo.
253 if ($_REQUEST['start'] == 'new')
254 {
255 // Guests automatically go to the last post.
256 if ($user_info['is_guest'])
257 {
258 $context['start_from'] = $context['total_visible_posts'] - 1;
259 $_REQUEST['start'] = empty($options['view_newest_first']) ? $context['start_from'] : 0;
260 }
261 else
262 {
263 // Find the earliest unread message in the topic. (the use of topics here is just for both tables.)
264 $request = $smcFunc['db_query']('', '
265 SELECT IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from
266 FROM {db_prefix}topics AS t
267 LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = {int:current_topic} AND lt.id_member = {int:current_member})
268 LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:current_board} AND lmr.id_member = {int:current_member})
269 WHERE t.id_topic = {int:current_topic}
270 LIMIT 1',
271 array(
272 'current_board' => $board,
273 'current_member' => $user_info['id'],
274 'current_topic' => $topic,
275 )
276 );
277 list ($new_from) = $smcFunc['db_fetch_row']($request);
278 $smcFunc['db_free_result']($request);
279
280 // Fall through to the next if statement.
281 $_REQUEST['start'] = 'msg' . $new_from;
282 }
283 }
284
285 // Start from a certain time index, not a message.
286 if (substr($_REQUEST['start'], 0, 4) == 'from')
287 {
288 $timestamp = (int) substr($_REQUEST['start'], 4);
289 if ($timestamp === 0)
290 $_REQUEST['start'] = 0;
291 else
292 {
293 // Find the number of messages posted before said time...
294 $request = $smcFunc['db_query']('', '
295 SELECT COUNT(*)
296 FROM {db_prefix}messages
297 WHERE poster_time < {int:timestamp}
298 AND id_topic = {int:current_topic}' . ($modSettings['postmod_active'] && $topicinfo['unapproved_posts'] && !allowedTo('approve_posts') ? '
299 AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')' : ''),
300 array(
301 'current_topic' => $topic,
302 'current_member' => $user_info['id'],
303 'is_approved' => 1,
304 'timestamp' => $timestamp,
305 )
306 );
307 list ($context['start_from']) = $smcFunc['db_fetch_row']($request);
308 $smcFunc['db_free_result']($request);
309
310 // Handle view_newest_first options, and get the correct start value.
311 $_REQUEST['start'] = empty($options['view_newest_first']) ? $context['start_from'] : $context['total_visible_posts'] - $context['start_from'] - 1;
312 }
313 }
314
315 // Link to a message...
316 elseif (substr($_REQUEST['start'], 0, 3) == 'msg')
317 {
318 $virtual_msg = (int) substr($_REQUEST['start'], 3);
319 if (!$topicinfo['unapproved_posts'] && $virtual_msg >= $topicinfo['id_last_msg'])
320 $context['start_from'] = $context['total_visible_posts'] - 1;
321 elseif (!$topicinfo['unapproved_posts'] && $virtual_msg <= $topicinfo['id_first_msg'])
322 $context['start_from'] = 0;
323 else
324 {
325 // Find the start value for that message......
326 $request = $smcFunc['db_query']('', '
327 SELECT COUNT(*)
328 FROM {db_prefix}messages
329 WHERE id_msg < {int:virtual_msg}
330 AND id_topic = {int:current_topic}' . ($modSettings['postmod_active'] && $topicinfo['unapproved_posts'] && !allowedTo('approve_posts') ? '
331 AND (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')' : ''),
332 array(
333 'current_member' => $user_info['id'],
334 'current_topic' => $topic,
335 'virtual_msg' => $virtual_msg,
336 'is_approved' => 1,
337 'no_member' => 0,
338 )
339 );
340 list ($context['start_from']) = $smcFunc['db_fetch_row']($request);
341 $smcFunc['db_free_result']($request);
342 }
343
344 // We need to reverse the start as well in this case.
345 $_REQUEST['start'] = empty($options['view_newest_first']) ? $context['start_from'] : $context['total_visible_posts'] - $context['start_from'] - 1;
346 }
347 }
348
349 // Create a previous next string if the selected theme has it as a selected option.
350 $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>' : '';
351
352 // Check if spellchecking is both enabled and actually working. (for quick reply.)
353 $context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
354
355 // Do we need to show the visual verification image?
356 $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));
357 if ($context['require_verification'])
358 {
359 require_once($sourcedir . '/Subs-Editor.php');
360 $verificationOptions = array(
361 'id' => 'post',
362 );
363 $context['require_verification'] = create_control_verification($verificationOptions);
364 $context['visual_verification_id'] = $verificationOptions['id'];
365 }
366
367 // Are we showing signatures - or disabled fields?
368 $context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
369 $context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
370
371 // Censor the title...
372 censorText($topicinfo['subject']);
373 $context['page_title'] = $topicinfo['subject'];
374
375 // Is this topic sticky, or can it even be?
376 $topicinfo['is_sticky'] = empty($modSettings['enableStickyTopics']) ? '0' : $topicinfo['is_sticky'];
377
378 // Default this topic to not marked for notifications... of course...
379 $context['is_marked_notify'] = false;
380
381 // Did we report a post to a moderator just now?
382 $context['report_sent'] = isset($_GET['reportsent']);
383
384 // Let's get nosey, who is viewing this topic?
385 if (!empty($settings['display_who_viewing']))
386 {
387 // Start out with no one at all viewing it.
388 $context['view_members'] = array();
389 $context['view_members_list'] = array();
390 $context['view_num_hidden'] = 0;
391
392 // Search for members who have this topic set in their GET data.
393 $request = $smcFunc['db_query']('', '
394 SELECT
395 lo.id_member, lo.log_time, mem.real_name, mem.member_name, mem.show_online,
396 mg.online_color, mg.id_group, mg.group_name
397 FROM {db_prefix}log_online AS lo
398 LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lo.id_member)
399 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)
400 WHERE INSTR(lo.url, {string:in_url_string}) > 0 OR lo.session = {string:session}',
401 array(
402 'reg_id_group' => 0,
403 'in_url_string' => 's:5:"topic";i:' . $topic . ';',
404 'session' => $user_info['is_guest'] ? 'ip' . $user_info['ip'] : session_id(),
405 )
406 );
407 while ($row = $smcFunc['db_fetch_assoc']($request))
408 {
409 if (empty($row['id_member']))
410 continue;
411
412 if (!empty($row['online_color']))
413 $link = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '" style="color: ' . $row['online_color'] . ';">' . $row['real_name'] . '</a>';
414 else
415 $link = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
416
417 $is_buddy = in_array($row['id_member'], $user_info['buddies']);
418 if ($is_buddy)
419 $link = '<strong>' . $link . '</strong>';
420
421 // Add them both to the list and to the more detailed list.
422 if (!empty($row['show_online']) || allowedTo('moderate_forum'))
423 $context['view_members_list'][$row['log_time'] . $row['member_name']] = empty($row['show_online']) ? '<em>' . $link . '</em>' : $link;
424 $context['view_members'][$row['log_time'] . $row['member_name']] = array(
425 'id' => $row['id_member'],
426 'username' => $row['member_name'],
427 'name' => $row['real_name'],
428 'group' => $row['id_group'],
429 'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
430 'link' => $link,
431 'is_buddy' => $is_buddy,
432 'hidden' => empty($row['show_online']),
433 );
434
435 if (empty($row['show_online']))
436 $context['view_num_hidden']++;
437 }
438
439 // The number of guests is equal to the rows minus the ones we actually used ;).
440 $context['view_num_guests'] = $smcFunc['db_num_rows']($request) - count($context['view_members']);
441 $smcFunc['db_free_result']($request);
442
443 // Sort the list.
444 krsort($context['view_members']);
445 krsort($context['view_members_list']);
446 }
447
448 // If all is set, but not allowed... just unset it.
449 $can_show_all = !empty($modSettings['enableAllMessages']) && $context['total_visible_posts'] > $context['messages_per_page'] && $context['total_visible_posts'] < $modSettings['enableAllMessages'];
450 if (isset($_REQUEST['all']) && !$can_show_all)
451 unset($_REQUEST['all']);
452 // Otherwise, it must be allowed... so pretend start was -1.
453 elseif (isset($_REQUEST['all']))
454 $_REQUEST['start'] = -1;
455
456 // Construct the page index, allowing for the .START method...
457 $context['page_index'] = constructPageIndex($scripturl . '?topic=' . $topic . '.%1$d', $_REQUEST['start'], $context['total_visible_posts'], $context['messages_per_page'], true);
458 $context['start'] = $_REQUEST['start'];
459
460 // 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..)
461 $context['page_info'] = array(
462 'current_page' => $_REQUEST['start'] / $context['messages_per_page'] + 1,
463 'num_pages' => floor(($context['total_visible_posts'] - 1) / $context['messages_per_page']) + 1,
464 );
465
466 // Figure out all the link to the next/prev/first/last/etc. for wireless mainly.
467 $context['links'] = array(
468 'first' => $_REQUEST['start'] >= $context['messages_per_page'] ? $scripturl . '?topic=' . $topic . '.0' : '',
469 'prev' => $_REQUEST['start'] >= $context['messages_per_page'] ? $scripturl . '?topic=' . $topic . '.' . ($_REQUEST['start'] - $context['messages_per_page']) : '',
470 'next' => $_REQUEST['start'] + $context['messages_per_page'] < $context['total_visible_posts'] ? $scripturl . '?topic=' . $topic. '.' . ($_REQUEST['start'] + $context['messages_per_page']) : '',
471 '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']) : '',
472 'up' => $scripturl . '?board=' . $board . '.0'
473 );
474
475 // If they are viewing all the posts, show all the posts, otherwise limit the number.
476 if ($can_show_all)
477 {
478 if (isset($_REQUEST['all']))
479 {
480 // No limit! (actually, there is a limit, but...)
481 $context['messages_per_page'] = -1;
482 $context['page_index'] .= empty($modSettings['compactTopicPagesEnable']) ? '<strong>' . $txt['all'] . '</strong> ' : '[<strong>' . $txt['all'] . '</strong>] ';
483
484 // Set start back to 0...
485 $_REQUEST['start'] = 0;
486 }
487 // They aren't using it, but the *option* is there, at least.
488 else
489 $context['page_index'] .= '&nbsp;<a href="' . $scripturl . '?topic=' . $topic . '.0;all">' . $txt['all'] . '</a> ';
490 }
491
492 // Build the link tree.
493 $context['linktree'][] = array(
494 'url' => $scripturl . '?topic=' . $topic . '.0',
495 'name' => $topicinfo['subject'],
496 'extra_before' => $settings['linktree_inline'] ? $txt['topic'] . ': ' : ''
497 );
498
499 // Build a list of this board's moderators.
500 $context['moderators'] = &$board_info['moderators'];
501 $context['link_moderators'] = array();
502 if (!empty($board_info['moderators']))
503 {
504 // Add a link for each moderator...
505 foreach ($board_info['moderators'] as $mod)
506 $context['link_moderators'][] = '<a href="' . $scripturl . '?action=profile;u=' . $mod['id'] . '" title="' . $txt['board_moderator'] . '">' . $mod['name'] . '</a>';
507
508 // And show it after the board's name.
509 $context['linktree'][count($context['linktree']) - 2]['extra_after'] = ' (' . (count($context['link_moderators']) == 1 ? $txt['moderator'] : $txt['moderators']) . ': ' . implode(', ', $context['link_moderators']) . ')';
510 }
511
512 // Information about the current topic...
513 $context['is_locked'] = $topicinfo['locked'];
514 $context['is_sticky'] = $topicinfo['is_sticky'];
515 $context['is_very_hot'] = $topicinfo['num_replies'] >= $modSettings['hotTopicVeryPosts'];
516 $context['is_hot'] = $topicinfo['num_replies'] >= $modSettings['hotTopicPosts'];
517 $context['is_approved'] = $topicinfo['approved'];
518
519 // We don't want to show the poll icon in the topic class here, so pretend it's not one.
520 $context['is_poll'] = false;
521 determineTopicClass($context);
522
523 $context['is_poll'] = $topicinfo['id_poll'] > 0 && $modSettings['pollMode'] == '1' && allowedTo('poll_view');
524
525 // Did this user start the topic or not?
526 $context['user']['started'] = $user_info['id'] == $topicinfo['id_member_started'] && !$user_info['is_guest'];
527 $context['topic_starter_id'] = $topicinfo['id_member_started'];
528
529 // Set the topic's information for the template.
530 $context['subject'] = $topicinfo['subject'];
531 $context['num_views'] = $topicinfo['num_views'];
532 $context['mark_unread_time'] = $topicinfo['new_from'];
533
534 // Set a canonical URL for this page.
535 $context['canonical_url'] = $scripturl . '?topic=' . $topic . '.' . $context['start'];
536
537 // For quick reply we need a response prefix in the default forum language.
538 if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix', 600)))
539 {
540 if ($language === $user_info['language'])
541 $context['response_prefix'] = $txt['response_prefix'];
542 else
543 {
544 loadLanguage('index', $language, false);
545 $context['response_prefix'] = $txt['response_prefix'];
546 loadLanguage('index');
547 }
548 cache_put_data('response_prefix', $context['response_prefix'], 600);
549 }
550
551 // If we want to show event information in the topic, prepare the data.
552 if (allowedTo('calendar_view') && !empty($modSettings['cal_showInTopic']) && !empty($modSettings['cal_enabled']))
553 {
554 // First, try create a better time format, ignoring the "time" elements.
555 if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
556 $date_string = $user_info['time_format'];
557 else
558 $date_string = $matches[0];
559
560 // Any calendar information for this topic?
561 $request = $smcFunc['db_query']('', '
562 SELECT cal.id_event, cal.start_date, cal.end_date, cal.title, cal.id_member, mem.real_name
563 FROM {db_prefix}calendar AS cal
564 LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = cal.id_member)
565 WHERE cal.id_topic = {int:current_topic}
566 ORDER BY start_date',
567 array(
568 'current_topic' => $topic,
569 )
570 );
571 $context['linked_calendar_events'] = array();
572 while ($row = $smcFunc['db_fetch_assoc']($request))
573 {
574 // Prepare the dates for being formatted.
575 $start_date = sscanf($row['start_date'], '%04d-%02d-%02d');
576 $start_date = mktime(12, 0, 0, $start_date[1], $start_date[2], $start_date[0]);
577 $end_date = sscanf($row['end_date'], '%04d-%02d-%02d');
578 $end_date = mktime(12, 0, 0, $end_date[1], $end_date[2], $end_date[0]);
579
580 $context['linked_calendar_events'][] = array(
581 'id' => $row['id_event'],
582 'title' => $row['title'],
583 'can_edit' => allowedTo('calendar_edit_any') || ($row['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own')),
584 'modify_href' => $scripturl . '?action=post;msg=' . $topicinfo['id_first_msg'] . ';topic=' . $topic . '.0;calendar;eventid=' . $row['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'],
585 'start_date' => timeformat($start_date, $date_string, 'none'),
586 'start_timestamp' => $start_date,
587 'end_date' => timeformat($end_date, $date_string, 'none'),
588 'end_timestamp' => $end_date,
589 'is_last' => false
590 );
591 }
592 $smcFunc['db_free_result']($request);
593
594 if (!empty($context['linked_calendar_events']))
595 $context['linked_calendar_events'][count($context['linked_calendar_events']) - 1]['is_last'] = true;
596 }
597
598 // Create the poll info if it exists.
599 if ($context['is_poll'])
600 {
601 // Get the question and if it's locked.
602 $request = $smcFunc['db_query']('', '
603 SELECT
604 p.question, p.voting_locked, p.hide_results, p.expire_time, p.max_votes, p.change_vote,
605 p.guest_vote, p.id_member, IFNULL(mem.real_name, p.poster_name) AS poster_name, p.num_guest_voters, p.reset_poll
606 FROM {db_prefix}polls AS p
607 LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = p.id_member)
608 WHERE p.id_poll = {int:id_poll}
609 LIMIT 1',
610 array(
611 'id_poll' => $topicinfo['id_poll'],
612 )
613 );
614 $pollinfo = $smcFunc['db_fetch_assoc']($request);
615 $smcFunc['db_free_result']($request);
616
617 $request = $smcFunc['db_query']('', '
618 SELECT COUNT(DISTINCT id_member) AS total
619 FROM {db_prefix}log_polls
620 WHERE id_poll = {int:id_poll}
621 AND id_member != {int:not_guest}',
622 array(
623 'id_poll' => $topicinfo['id_poll'],
624 'not_guest' => 0,
625 )
626 );
627 list ($pollinfo['total']) = $smcFunc['db_fetch_row']($request);
628 $smcFunc['db_free_result']($request);
629
630 // Total voters needs to include guest voters
631 $pollinfo['total'] += $pollinfo['num_guest_voters'];
632
633 // Get all the options, and calculate the total votes.
634 $request = $smcFunc['db_query']('', '
635 SELECT pc.id_choice, pc.label, pc.votes, IFNULL(lp.id_choice, -1) AS voted_this
636 FROM {db_prefix}poll_choices AS pc
637 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})
638 WHERE pc.id_poll = {int:id_poll}',
639 array(
640 'current_member' => $user_info['id'],
641 'id_poll' => $topicinfo['id_poll'],
642 'not_guest' => 0,
643 )
644 );
645 $pollOptions = array();
646 $realtotal = 0;
647 $pollinfo['has_voted'] = false;
648 while ($row = $smcFunc['db_fetch_assoc']($request))
649 {
650 censorText($row['label']);
651 $pollOptions[$row['id_choice']] = $row;
652 $realtotal += $row['votes'];
653 $pollinfo['has_voted'] |= $row['voted_this'] != -1;
654 }
655 $smcFunc['db_free_result']($request);
656
657 // If this is a guest we need to do our best to work out if they have voted, and what they voted for.
658 if ($user_info['is_guest'] && $pollinfo['guest_vote'] && allowedTo('poll_vote'))
659 {
660 if (!empty($_COOKIE['guest_poll_vote']) && preg_match('~^[0-9,;]+$~', $_COOKIE['guest_poll_vote']) && strpos($_COOKIE['guest_poll_vote'], ';' . $topicinfo['id_poll'] . ',') !== false)
661 {
662 // ;id,timestamp,[vote,vote...]; etc
663 $guestinfo = explode(';', $_COOKIE['guest_poll_vote']);
664 // Find the poll we're after.
665 foreach ($guestinfo as $i => $guestvoted)
666 {
667 $guestvoted = explode(',', $guestvoted);
668 if ($guestvoted[0] == $topicinfo['id_poll'])
669 break;
670 }
671 // Has the poll been reset since guest voted?
672 if ($pollinfo['reset_poll'] > $guestvoted[1])
673 {
674 // Remove the poll info from the cookie to allow guest to vote again
675 unset($guestinfo[$i]);
676 if (!empty($guestinfo))
677 $_COOKIE['guest_poll_vote'] = ';' . implode(';', $guestinfo);
678 else
679 unset($_COOKIE['guest_poll_vote']);
680 }
681 else
682 {
683 // What did they vote for?
684 unset($guestvoted[0], $guestvoted[1]);
685 foreach ($pollOptions as $choice => $details)
686 {
687 $pollOptions[$choice]['voted_this'] = in_array($choice, $guestvoted) ? 1 : -1;
688 $pollinfo['has_voted'] |= $pollOptions[$choice]['voted_this'] != -1;
689 }
690 unset($choice, $details, $guestvoted);
691 }
692 unset($guestinfo, $guestvoted, $i);
693 }
694 }
695
696 // Set up the basic poll information.
697 $context['poll'] = array(
698 'id' => $topicinfo['id_poll'],
699 'image' => 'normal_' . (empty($pollinfo['voting_locked']) ? 'poll' : 'locked_poll'),
700 'question' => parse_bbc($pollinfo['question']),
701 'total_votes' => $pollinfo['total'],
702 'change_vote' => !empty($pollinfo['change_vote']),
703 'is_locked' => !empty($pollinfo['voting_locked']),
704 'options' => array(),
705 'lock' => allowedTo('poll_lock_any') || ($context['user']['started'] && allowedTo('poll_lock_own')),
706 'edit' => allowedTo('poll_edit_any') || ($context['user']['started'] && allowedTo('poll_edit_own')),
707 'allowed_warning' => $pollinfo['max_votes'] > 1 ? sprintf($txt['poll_options6'], min(count($pollOptions), $pollinfo['max_votes'])) : '',
708 'is_expired' => !empty($pollinfo['expire_time']) && $pollinfo['expire_time'] < time(),
709 'expire_time' => !empty($pollinfo['expire_time']) ? timeformat($pollinfo['expire_time']) : 0,
710 'has_voted' => !empty($pollinfo['has_voted']),
711 'starter' => array(
712 'id' => $pollinfo['id_member'],
713 'name' => $row['poster_name'],
714 'href' => $pollinfo['id_member'] == 0 ? '' : $scripturl . '?action=profile;u=' . $pollinfo['id_member'],
715 'link' => $pollinfo['id_member'] == 0 ? $row['poster_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $pollinfo['id_member'] . '">' . $row['poster_name'] . '</a>'
716 )
717 );
718
719 // Make the lock and edit permissions defined above more directly accessible.
720 $context['allow_lock_poll'] = $context['poll']['lock'];
721 $context['allow_edit_poll'] = $context['poll']['edit'];
722
723 // You're allowed to vote if:
724 // 1. the poll did not expire, and
725 // 2. you're either not a guest OR guest voting is enabled... and
726 // 3. you're not trying to view the results, and
727 // 4. the poll is not locked, and
728 // 5. you have the proper permissions, and
729 // 6. you haven't already voted before.
730 $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'];
731
732 // You're allowed to view the results if:
733 // 1. you're just a super-nice-guy, or
734 // 2. anyone can see them (hide_results == 0), or
735 // 3. you can see them after you voted (hide_results == 1), or
736 // 4. you've waited long enough for the poll to expire. (whether hide_results is 1 or 2.)
737 $context['allow_poll_view'] = allowedTo('moderate_board') || $pollinfo['hide_results'] == 0 || ($pollinfo['hide_results'] == 1 && $context['poll']['has_voted']) || $context['poll']['is_expired'];
738 $context['poll']['show_results'] = $context['allow_poll_view'] && (isset($_REQUEST['viewresults']) || isset($_REQUEST['viewResults']));
739 $context['show_view_results_button'] = $context['allow_vote'] && (!$context['allow_poll_view'] || !$context['poll']['show_results'] || !$context['poll']['has_voted']);
740
741 // You're allowed to change your vote if:
742 // 1. the poll did not expire, and
743 // 2. you're not a guest... and
744 // 3. the poll is not locked, and
745 // 4. you have the proper permissions, and
746 // 5. you have already voted, and
747 // 6. the poll creator has said you can!
748 $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'];
749
750 // You're allowed to return to voting options if:
751 // 1. you are (still) allowed to vote.
752 // 2. you are currently seeing the results.
753 $context['allow_return_vote'] = $context['allow_vote'] && $context['poll']['show_results'];
754
755 // Calculate the percentages and bar lengths...
756 $divisor = $realtotal == 0 ? 1 : $realtotal;
757
758 // Determine if a decimal point is needed in order for the options to add to 100%.
759 $precision = $realtotal == 100 ? 0 : 1;
760
761 // Now look through each option, and...
762 foreach ($pollOptions as $i => $option)
763 {
764 // First calculate the percentage, and then the width of the bar...
765 $bar = round(($option['votes'] * 100) / $divisor, $precision);
766 $barWide = $bar == 0 ? 1 : floor(($bar * 8) / 3);
767
768 // Now add it to the poll's contextual theme data.
769 $context['poll']['options'][$i] = array(
770 'id' => 'options-' . $i,
771 'percent' => $bar,
772 'votes' => $option['votes'],
773 'voted_this' => $option['voted_this'] != -1,
774 'bar' => '<span style="white-space: nowrap;"><img src="' . $settings['images_url'] . '/poll_' . ($context['right_to_left'] ? 'right' : 'left') . '.gif" alt="" /><img src="' . $settings['images_url'] . '/poll_middle.gif" width="' . $barWide . '" height="12" alt="-" /><img src="' . $settings['images_url'] . '/poll_' . ($context['right_to_left'] ? 'left' : 'right') . '.gif" alt="" /></span>',
775 // Note: IE < 8 requires us to set a width on the container, too.
776 'bar_ndt' => $bar > 0 ? '<div class="bar" style="width: ' . ($bar * 3.5 + 4) . 'px;"><div style="width: ' . $bar * 3.5 . 'px;"></div></div>' : '',
777 'bar_width' => $barWide,
778 'option' => parse_bbc($option['label']),
779 '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') . '" />'
780 );
781 }
782 }
783
784 // Calculate the fastest way to get the messages!
785 $ascending = empty($options['view_newest_first']);
786 $start = $_REQUEST['start'];
787 $limit = $context['messages_per_page'];
788 $firstIndex = 0;
789 if ($start >= $context['total_visible_posts'] / 2 && $context['messages_per_page'] != -1)
790 {
791 $ascending = !$ascending;
792 $limit = $context['total_visible_posts'] <= $start + $limit ? $context['total_visible_posts'] - $start : $limit;
793 $start = $context['total_visible_posts'] <= $start + $limit ? 0 : $context['total_visible_posts'] - $start - $limit;
794 $firstIndex = $limit - 1;
795 }
796
797 // Get each post and poster in this topic.
798 $request = $smcFunc['db_query']('display_get_post_poster', '
799 SELECT id_msg, id_member, approved
800 FROM {db_prefix}messages
801 WHERE id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : (!empty($modSettings['db_mysql_group_by_fix']) ? '' : '
802 GROUP BY id_msg') . '
803 HAVING (approved = {int:is_approved}' . ($user_info['is_guest'] ? '' : ' OR id_member = {int:current_member}') . ')') . '
804 ORDER BY id_msg ' . ($ascending ? '' : 'DESC') . ($context['messages_per_page'] == -1 ? '' : '
805 LIMIT ' . $start . ', ' . $limit),
806 array(
807 'current_member' => $user_info['id'],
808 'current_topic' => $topic,
809 'is_approved' => 1,
810 'blank_id_member' => 0,
811 )
812 );
813
814 $messages = array();
815 $all_posters = array();
816 while ($row = $smcFunc['db_fetch_assoc']($request))
817 {
818 if (!empty($row['id_member']))
819 $all_posters[$row['id_msg']] = $row['id_member'];
820 $messages[] = $row['id_msg'];
821 }
822 $smcFunc['db_free_result']($request);
823 $posters = array_unique($all_posters);
824
825 // Guests can't mark topics read or for notifications, just can't sorry.
826 if (!$user_info['is_guest'])
827 {
828 $mark_at_msg = max($messages);
829 if ($mark_at_msg >= $topicinfo['id_last_msg'])
830 $mark_at_msg = $modSettings['maxMsgID'];
831 if ($mark_at_msg >= $topicinfo['new_from'])
832 {
833 $smcFunc['db_insert']($topicinfo['new_from'] == 0 ? 'ignore' : 'replace',
834 '{db_prefix}log_topics',
835 array(
836 'id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int',
837 ),
838 array(
839 $user_info['id'], $topic, $mark_at_msg,
840 ),
841 array('id_member', 'id_topic')
842 );
843 }
844
845 // Check for notifications on this topic OR board.
846 $request = $smcFunc['db_query']('', '
847 SELECT sent, id_topic
848 FROM {db_prefix}log_notify
849 WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
850 AND id_member = {int:current_member}
851 LIMIT 2',
852 array(
853 'current_board' => $board,
854 'current_member' => $user_info['id'],
855 'current_topic' => $topic,
856 )
857 );
858 $do_once = true;
859 while ($row = $smcFunc['db_fetch_assoc']($request))
860 {
861 // Find if this topic is marked for notification...
862 if (!empty($row['id_topic']))
863 $context['is_marked_notify'] = true;
864
865 // Only do this once, but mark the notifications as "not sent yet" for next time.
866 if (!empty($row['sent']) && $do_once)
867 {
868 $smcFunc['db_query']('', '
869 UPDATE {db_prefix}log_notify
870 SET sent = {int:is_not_sent}
871 WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
872 AND id_member = {int:current_member}',
873 array(
874 'current_board' => $board,
875 'current_member' => $user_info['id'],
876 'current_topic' => $topic,
877 'is_not_sent' => 0,
878 )
879 );
880 $do_once = false;
881 }
882 }
883
884 // Have we recently cached the number of new topics in this board, and it's still a lot?
885 if (isset($_REQUEST['topicseen']) && isset($_SESSION['topicseen_cache'][$board]) && $_SESSION['topicseen_cache'][$board] > 5)
886 $_SESSION['topicseen_cache'][$board]--;
887 // Mark board as seen if this is the only new topic.
888 elseif (isset($_REQUEST['topicseen']))
889 {
890 // Use the mark read tables... and the last visit to figure out if this should be read or not.
891 $request = $smcFunc['db_query']('', '
892 SELECT COUNT(*)
893 FROM {db_prefix}topics AS t
894 LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = {int:current_board} AND lb.id_member = {int:current_member})
895 LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
896 WHERE t.id_board = {int:current_board}
897 AND t.id_last_msg > IFNULL(lb.id_msg, 0)
898 AND t.id_last_msg > IFNULL(lt.id_msg, 0)' . (empty($_SESSION['id_msg_last_visit']) ? '' : '
899 AND t.id_last_msg > {int:id_msg_last_visit}'),
900 array(
901 'current_board' => $board,
902 'current_member' => $user_info['id'],
903 'id_msg_last_visit' => (int) $_SESSION['id_msg_last_visit'],
904 )
905 );
906 list ($numNewTopics) = $smcFunc['db_fetch_row']($request);
907 $smcFunc['db_free_result']($request);
908
909 // If there're no real new topics in this board, mark the board as seen.
910 if (empty($numNewTopics))
911 $_REQUEST['boardseen'] = true;
912 else
913 $_SESSION['topicseen_cache'][$board] = $numNewTopics;
914 }
915 // Probably one less topic - maybe not, but even if we decrease this too fast it will only make us look more often.
916 elseif (isset($_SESSION['topicseen_cache'][$board]))
917 $_SESSION['topicseen_cache'][$board]--;
918
919 // Mark board as seen if we came using last post link from BoardIndex. (or other places...)
920 if (isset($_REQUEST['boardseen']))
921 {
922 $smcFunc['db_insert']('replace',
923 '{db_prefix}log_boards',
924 array('id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'),
925 array($modSettings['maxMsgID'], $user_info['id'], $board),
926 array('id_member', 'id_board')
927 );
928 }
929 }
930
931 $attachments = array();
932
933 // If there _are_ messages here... (probably an error otherwise :!)
934 if (!empty($messages))
935 {
936 // Fetch attachments.
937 if (!empty($modSettings['attachmentEnable']) && allowedTo('view_attachments'))
938 {
939 $request = $smcFunc['db_query']('', '
940 SELECT
941 a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, IFNULL(a.size, 0) AS filesize, a.downloads, a.approved,
942 a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
943 IFNULL(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
944 FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
945 LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
946 WHERE a.id_msg IN ({array_int:message_list})
947 AND a.attachment_type = {int:attachment_type}',
948 array(
949 'message_list' => $messages,
950 'attachment_type' => 0,
951 'is_approved' => 1,
952 )
953 );
954 $temp = array();
955 while ($row = $smcFunc['db_fetch_assoc']($request))
956 {
957 if (!$row['approved'] && $modSettings['postmod_active'] && !allowedTo('approve_posts') && (!isset($all_posters[$row['id_msg']]) || $all_posters[$row['id_msg']] != $user_info['id']))
958 continue;
959
960 $temp[$row['id_attach']] = $row;
961
962 if (!isset($attachments[$row['id_msg']]))
963 $attachments[$row['id_msg']] = array();
964 }
965 $smcFunc['db_free_result']($request);
966
967 // This is better than sorting it with the query...
968 ksort($temp);
969
970 foreach ($temp as $row)
971 $attachments[$row['id_msg']][] = $row;
972 }
973
974 // What? It's not like it *couldn't* be only guests in this topic...
975 if (!empty($posters))
976 loadMemberData($posters);
977 $messages_request = $smcFunc['db_query']('', '
978 SELECT
979 id_msg, icon, subject, poster_time, poster_ip, id_member, modified_time, modified_name, body,
980 smileys_enabled, poster_name, poster_email, approved,
981 id_msg_modified < {int:new_from} AS is_read
982 FROM {db_prefix}messages
983 WHERE id_msg IN ({array_int:message_list})
984 ORDER BY id_msg' . (empty($options['view_newest_first']) ? '' : ' DESC'),
985 array(
986 'message_list' => $messages,
987 'new_from' => $topicinfo['new_from'],
988 )
989 );
990
991 // Go to the last message if the given time is beyond the time of the last message.
992 if (isset($context['start_from']) && $context['start_from'] >= $topicinfo['num_replies'])
993 $context['start_from'] = $topicinfo['num_replies'];
994
995 // Since the anchor information is needed on the top of the page we load these variables beforehand.
996 $context['first_message'] = isset($messages[$firstIndex]) ? $messages[$firstIndex] : $messages[0];
997 if (empty($options['view_newest_first']))
998 $context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $context['start_from'];
999 else
1000 $context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $topicinfo['num_replies'] - $context['start_from'];
1001 }
1002 else
1003 {
1004 $messages_request = false;
1005 $context['first_message'] = 0;
1006 $context['first_new_message'] = false;
1007 }
1008
1009 $context['jump_to'] = array(
1010 'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
1011 'board_name' => htmlspecialchars(strtr(strip_tags($board_info['name']), array('&amp;' => '&'))),
1012 'child_level' => $board_info['child_level'],
1013 );
1014
1015 // Set the callback. (do you REALIZE how much memory all the messages would take?!?)
1016 $context['get_message'] = 'prepareDisplayContext';
1017
1018 // Now set all the wonderful, wonderful permissions... like moderation ones...
1019 $common_permissions = array(
1020 'can_approve' => 'approve_posts',
1021 'can_ban' => 'manage_bans',
1022 'can_sticky' => 'make_sticky',
1023 'can_merge' => 'merge_any',
1024 'can_split' => 'split_any',
1025 'calendar_post' => 'calendar_post',
1026 'can_mark_notify' => 'mark_any_notify',
1027 'can_send_topic' => 'send_topic',
1028 'can_send_pm' => 'pm_send',
1029 'can_report_moderator' => 'report_any',
1030 'can_moderate_forum' => 'moderate_forum',
1031 'can_issue_warning' => 'issue_warning',
1032 'can_restore_topic' => 'move_any',
1033 'can_restore_msg' => 'move_any',
1034 );
1035 foreach ($common_permissions as $contextual => $perm)
1036 $context[$contextual] = allowedTo($perm);
1037
1038 // Permissions with _any/_own versions. $context[YYY] => ZZZ_any/_own.
1039 $anyown_permissions = array(
1040 'can_move' => 'move',
1041 'can_lock' => 'lock',
1042 'can_delete' => 'remove',
1043 'can_add_poll' => 'poll_add',
1044 'can_remove_poll' => 'poll_remove',
1045 'can_reply' => 'post_reply',
1046 'can_reply_unapproved' => 'post_unapproved_replies',
1047 );
1048 foreach ($anyown_permissions as $contextual => $perm)
1049 $context[$contextual] = allowedTo($perm . '_any') || ($context['user']['started'] && allowedTo($perm . '_own'));
1050
1051 // Cleanup all the permissions with extra stuff...
1052 $context['can_mark_notify'] &= !$context['user']['is_guest'];
1053 $context['can_sticky'] &= !empty($modSettings['enableStickyTopics']);
1054 $context['calendar_post'] &= !empty($modSettings['cal_enabled']);
1055 $context['can_add_poll'] &= $modSettings['pollMode'] == '1' && $topicinfo['id_poll'] <= 0;
1056 $context['can_remove_poll'] &= $modSettings['pollMode'] == '1' && $topicinfo['id_poll'] > 0;
1057 $context['can_reply'] &= empty($topicinfo['locked']) || allowedTo('moderate_board');
1058 $context['can_reply_unapproved'] &= $modSettings['postmod_active'] && (empty($topicinfo['locked']) || allowedTo('moderate_board'));
1059 $context['can_issue_warning'] &= in_array('w', $context['admin_features']) && $modSettings['warning_settings'][0] == 1;
1060 // Handle approval flags...
1061 $context['can_reply_approved'] = $context['can_reply'];
1062 $context['can_reply'] |= $context['can_reply_unapproved'];
1063 $context['can_quote'] = $context['can_reply'] && (empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])));
1064 $context['can_mark_unread'] = !$user_info['is_guest'] && $settings['show_mark_read'];
1065
1066 $context['can_send_topic'] = (!$modSettings['postmod_active'] || $topicinfo['approved']) && allowedTo('send_topic');
1067
1068 // Start this off for quick moderation - it will be or'd for each post.
1069 $context['can_remove_post'] = allowedTo('delete_any') || (allowedTo('delete_replies') && $context['user']['started']);
1070
1071 // Can restore topic? That's if the topic is in the recycle board and has a previous restore state.
1072 $context['can_restore_topic'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_board']);
1073 $context['can_restore_msg'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_topic']);
1074
1075 // Wireless shows a "more" if you can do anything special.
1076 if (WIRELESS && WIRELESS_PROTOCOL != 'wap')
1077 {
1078 $context['wireless_more'] = $context['can_sticky'] || $context['can_lock'] || allowedTo('modify_any');
1079 $context['wireless_moderate'] = isset($_GET['moderate']) ? ';moderate' : '';
1080 }
1081
1082 // Load up the "double post" sequencing magic.
1083 if (!empty($options['display_quick_reply']))
1084 {
1085 checkSubmitOnce('register');
1086 $context['name'] = isset($_SESSION['guest_name']) ? $_SESSION['guest_name'] : '';
1087 $context['email'] = isset($_SESSION['guest_email']) ? $_SESSION['guest_email'] : '';
1088 }
1089 }
1090
1091 // Callback for the message display.
1092 function prepareDisplayContext($reset = false)
1093 {
1094 global $settings, $txt, $modSettings, $scripturl, $options, $user_info, $smcFunc;
1095 global $memberContext, $context, $messages_request, $topic, $attachments, $topicinfo;
1096
1097 static $counter = null;
1098
1099 // If the query returned false, bail.
1100 if ($messages_request == false)
1101 return false;
1102
1103 // Remember which message this is. (ie. reply #83)
1104 if ($counter === null || $reset)
1105 $counter = empty($options['view_newest_first']) ? $context['start'] : $context['total_visible_posts'] - $context['start'];
1106
1107 // Start from the beginning...
1108 if ($reset)
1109 return @$smcFunc['db_data_seek']($messages_request, 0);
1110
1111 // Attempt to get the next message.
1112 $message = $smcFunc['db_fetch_assoc']($messages_request);
1113 if (!$message)
1114 {
1115 $smcFunc['db_free_result']($messages_request);
1116 return false;
1117 }
1118
1119 // $context['icon_sources'] says where each icon should come from - here we set up the ones which will always exist!
1120 if (empty($context['icon_sources']))
1121 {
1122 $stable_icons = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'moved', 'recycled', 'wireless', 'clip');
1123 $context['icon_sources'] = array();
1124 foreach ($stable_icons as $icon)
1125 $context['icon_sources'][$icon] = 'images_url';
1126 }
1127
1128 // Message Icon Management... check the images exist.
1129 if (empty($modSettings['messageIconChecks_disable']))
1130 {
1131 // If the current icon isn't known, then we need to do something...
1132 if (!isset($context['icon_sources'][$message['icon']]))
1133 $context['icon_sources'][$message['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['icon'] . '.gif') ? 'images_url' : 'default_images_url';
1134 }
1135 elseif (!isset($context['icon_sources'][$message['icon']]))
1136 $context['icon_sources'][$message['icon']] = 'images_url';
1137
1138 // If you're a lazy bum, you probably didn't give a subject...
1139 $message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject'];
1140
1141 // Are you allowed to remove at least a single reply?
1142 $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'];
1143
1144 // If it couldn't load, or the user was a guest.... someday may be done with a guest table.
1145 if (!loadMemberContext($message['id_member'], true))
1146 {
1147 // Notice this information isn't used anywhere else....
1148 $memberContext[$message['id_member']]['name'] = $message['poster_name'];
1149 $memberContext[$message['id_member']]['id'] = 0;
1150 $memberContext[$message['id_member']]['group'] = $txt['guest_title'];
1151 $memberContext[$message['id_member']]['link'] = $message['poster_name'];
1152 $memberContext[$message['id_member']]['email'] = $message['poster_email'];
1153 $memberContext[$message['id_member']]['show_email'] = showEmailAddress(true, 0);
1154 $memberContext[$message['id_member']]['is_guest'] = true;
1155 }
1156 else
1157 {
1158 $memberContext[$message['id_member']]['can_view_profile'] = allowedTo('profile_view_any') || ($message['id_member'] == $user_info['id'] && allowedTo('profile_view_own'));
1159 $memberContext[$message['id_member']]['is_topic_starter'] = $message['id_member'] == $context['topic_starter_id'];
1160 $memberContext[$message['id_member']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member']]['warning_status'] && ($context['user']['can_mod'] || (!$user_info['is_guest'] && !empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $message['id_member'] == $user_info['id'])));
1161 }
1162
1163 $memberContext[$message['id_member']]['ip'] = $message['poster_ip'];
1164
1165 // Do the censor thang.
1166 censorText($message['body']);
1167 censorText($message['subject']);
1168
1169 // Run BBC interpreter on the message.
1170 $message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1171
1172 // Compose the memory eat- I mean message array.
1173 $output = array(
1174 'attachment' => loadAttachmentContext($message['id_msg']),
1175 'alternate' => $counter % 2,
1176 'id' => $message['id_msg'],
1177 'href' => $scripturl . '?topic=' . $topic . '.msg' . $message['id_msg'] . '#msg' . $message['id_msg'],
1178 'link' => '<a href="' . $scripturl . '?topic=' . $topic . '.msg' . $message['id_msg'] . '#msg' . $message['id_msg'] . '" rel="nofollow">' . $message['subject'] . '</a>',
1179 'member' => &$memberContext[$message['id_member']],
1180 'icon' => $message['icon'],
1181 'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.gif',
1182 'subject' => $message['subject'],
1183 'time' => timeformat($message['poster_time']),
1184 'timestamp' => forum_time(true, $message['poster_time']),
1185 'counter' => $counter,
1186 'modified' => array(
1187 'time' => timeformat($message['modified_time']),
1188 'timestamp' => forum_time(true, $message['modified_time']),
1189 'name' => $message['modified_name']
1190 ),
1191 'body' => $message['body'],
1192 'new' => empty($message['is_read']),
1193 'approved' => $message['approved'],
1194 'first_new' => isset($context['start_from']) && $context['start_from'] == $counter,
1195 'is_ignored' => !empty($modSettings['enable_buddylist']) && !empty($options['posts_apply_ignore_list']) && in_array($message['id_member'], $context['user']['ignoreusers']),
1196 'can_approve' => !$message['approved'] && $context['can_approve'],
1197 'can_unapprove' => $message['approved'] && $context['can_approve'],
1198 '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()))),
1199 '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())),
1200 'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member'] == $user_info['id'] && !empty($user_info['id'])),
1201 );
1202
1203 // Is this user the message author?
1204 $output['is_message_author'] = $message['id_member'] == $user_info['id'];
1205
1206 if (empty($options['view_newest_first']))
1207 $counter++;
1208 else
1209 $counter--;
1210
1211 return $output;
1212 }
1213
1214 // Download an attachment.
1215 function Download()
1216 {
1217 global $txt, $modSettings, $user_info, $scripturl, $context, $sourcedir, $topic, $smcFunc;
1218
1219 // Some defaults that we need.
1220 $context['character_set'] = empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'ISO-8859-1' : $txt['lang_character_set']) : $modSettings['global_character_set'];
1221 $context['utf8'] = $context['character_set'] === 'UTF-8' && (strpos(strtolower(PHP_OS), 'win') === false || @version_compare(PHP_VERSION, '4.2.3') != -1);
1222 $context['no_last_modified'] = true;
1223
1224 // Make sure some attachment was requested!
1225 if (!isset($_REQUEST['attach']) && !isset($_REQUEST['id']))
1226 fatal_lang_error('no_access', false);
1227
1228 $_REQUEST['attach'] = isset($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : (int) $_REQUEST['id'];
1229
1230 if (isset($_REQUEST['type']) && $_REQUEST['type'] == 'avatar')
1231 {
1232 $request = $smcFunc['db_query']('', '
1233 SELECT id_folder, filename, file_hash, fileext, id_attach, attachment_type, mime_type, approved, id_member
1234 FROM {db_prefix}attachments
1235 WHERE id_attach = {int:id_attach}
1236 AND id_member > {int:blank_id_member}
1237 LIMIT 1',
1238 array(
1239 'id_attach' => $_REQUEST['attach'],
1240 'blank_id_member' => 0,
1241 )
1242 );
1243 $_REQUEST['image'] = true;
1244 }
1245 // This is just a regular attachment...
1246 else
1247 {
1248 // This checks only the current board for $board/$topic's permissions.
1249 isAllowedTo('view_attachments');
1250
1251 // Make sure this attachment is on this board.
1252 // NOTE: We must verify that $topic is the attachment's topic, or else the permission check above is broken.
1253 $request = $smcFunc['db_query']('', '
1254 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
1255 FROM {db_prefix}attachments AS a
1256 INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
1257 INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1258 WHERE a.id_attach = {int:attach}
1259 LIMIT 1',
1260 array(
1261 'attach' => $_REQUEST['attach'],
1262 'current_topic' => $topic,
1263 )
1264 );
1265 }
1266 if ($smcFunc['db_num_rows']($request) == 0)
1267 fatal_lang_error('no_access', false);
1268 list ($id_folder, $real_filename, $file_hash, $file_ext, $id_attach, $attachment_type, $mime_type, $is_approved, $id_member) = $smcFunc['db_fetch_row']($request);
1269 $smcFunc['db_free_result']($request);
1270
1271 // If it isn't yet approved, do they have permission to view it?
1272 if (!$is_approved && ($id_member == 0 || $user_info['id'] != $id_member) && ($attachment_type == 0 || $attachment_type == 3))
1273 isAllowedTo('approve_posts');
1274
1275 // Update the download counter (unless it's a thumbnail).
1276 if ($attachment_type != 3)
1277 $smcFunc['db_query']('attach_download_increase', '
1278 UPDATE LOW_PRIORITY {db_prefix}attachments
1279 SET downloads = downloads + 1
1280 WHERE id_attach = {int:id_attach}',
1281 array(
1282 'id_attach' => $id_attach,
1283 )
1284 );
1285
1286 $filename = getAttachmentFilename($real_filename, $_REQUEST['attach'], $id_folder, false, $file_hash);
1287
1288 // This is done to clear any output that was made before now. (would use ob_clean(), but that's PHP 4.2.0+...)
1289 ob_end_clean();
1290 if (!empty($modSettings['enableCompressedOutput']) && @version_compare(PHP_VERSION, '4.2.0') >= 0 && @filesize($filename) <= 4194304 && in_array($file_ext, array('txt', 'html', 'htm', 'js', 'doc', 'pdf', 'docx', 'rtf', 'css', 'php', 'log', 'xml', 'sql', 'c', 'java')))
1291 @ob_start('ob_gzhandler');
1292 else
1293 {
1294 ob_start();
1295 header('Content-Encoding: none');
1296 }
1297
1298 // No point in a nicer message, because this is supposed to be an attachment anyway...
1299 if (!file_exists($filename))
1300 {
1301 loadLanguage('Errors');
1302
1303 header('HTTP/1.0 404 ' . $txt['attachment_not_found']);
1304 header('Content-Type: text/plain; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
1305
1306 // We need to die like this *before* we send any anti-caching headers as below.
1307 die('404 - ' . $txt['attachment_not_found']);
1308 }
1309
1310 // If it hasn't been modified since the last time this attachement was retrieved, there's no need to display it again.
1311 if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
1312 {
1313 list($modified_since) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
1314 if (strtotime($modified_since) >= filemtime($filename))
1315 {
1316 ob_end_clean();
1317
1318 // Answer the question - no, it hasn't been modified ;).
1319 header('HTTP/1.1 304 Not Modified');
1320 exit;
1321 }
1322 }
1323
1324 // Check whether the ETag was sent back, and cache based on that...
1325 $eTag = '"' . substr($_REQUEST['attach'] . $real_filename . filemtime($filename), 0, 64) . '"';
1326 if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $eTag) !== false)
1327 {
1328 ob_end_clean();
1329
1330 header('HTTP/1.1 304 Not Modified');
1331 exit;
1332 }
1333
1334 // Send the attachment headers.
1335 header('Pragma: ');
1336 if (!$context['browser']['is_gecko'])
1337 header('Content-Transfer-Encoding: binary');
1338 header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT');
1339 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filename)) . ' GMT');
1340 header('Accept-Ranges: bytes');
1341 header('Connection: close');
1342 header('ETag: ' . $eTag);
1343
1344 // IE 6 just doesn't play nice. As dirty as this seems, it works.
1345 if ($context['browser']['is_ie6'] && isset($_REQUEST['image']))
1346 unset($_REQUEST['image']);
1347
1348 // Make sure the mime type warrants an inline display.
1349 elseif (isset($_REQUEST['image']) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0)
1350 unset($_REQUEST['image']);
1351
1352 // Does this have a mime type?
1353 elseif (!empty($mime_type) && (isset($_REQUEST['image']) || !in_array($file_ext, array('jpg', 'gif', 'jpeg', 'x-ms-bmp', 'png', 'psd', 'tiff', 'iff'))))
1354 header('Content-Type: ' . strtr($mime_type, array('image/bmp' => 'image/x-ms-bmp')));
1355
1356 else
1357 {
1358 header('Content-Type: ' . ($context['browser']['is_ie'] || $context['browser']['is_opera'] ? 'application/octetstream' : 'application/octet-stream'));
1359 if (isset($_REQUEST['image']))
1360 unset($_REQUEST['image']);
1361 }
1362
1363 // Convert the file to UTF-8, cuz most browsers dig that.
1364 $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);
1365 $fixchar = create_function('$n', '
1366 if ($n < 32)
1367 return \'\';
1368 elseif ($n < 128)
1369 return chr($n);
1370 elseif ($n < 2048)
1371 return chr(192 | $n >> 6) . chr(128 | $n & 63);
1372 elseif ($n < 65536)
1373 return chr(224 | $n >> 12) . chr(128 | $n >> 6 & 63) . chr(128 | $n & 63);
1374 else
1375 return chr(240 | $n >> 18) . chr(128 | $n >> 12 & 63) . chr(128 | $n >> 6 & 63) . chr(128 | $n & 63);');
1376
1377 $disposition = !isset($_REQUEST['image']) ? 'attachment' : 'inline';
1378
1379 // Different browsers like different standards...
1380 if ($context['browser']['is_firefox'])
1381 header('Content-Disposition: ' . $disposition . '; filename*="UTF-8\'\'' . preg_replace('~&#(\d{3,8});~e', '$fixchar(\'$1\')', $utf8name) . '"');
1382
1383 elseif ($context['browser']['is_opera'])
1384 header('Content-Disposition: ' . $disposition . '; filename="' . preg_replace('~&#(\d{3,8});~e', '$fixchar(\'$1\')', $utf8name) . '"');
1385
1386 elseif ($context['browser']['is_ie'])
1387 header('Content-Disposition: ' . $disposition . '; filename="' . urlencode(preg_replace('~&#(\d{3,8});~e', '$fixchar(\'$1\')', $utf8name)) . '"');
1388
1389 else
1390 header('Content-Disposition: ' . $disposition . '; filename="' . $utf8name . '"');
1391
1392 // If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE.
1393 if (!isset($_REQUEST['image']) && in_array($file_ext, array('gif', 'jpg', 'bmp', 'png', 'jpeg', 'tiff')))
1394 header('Cache-Control: no-cache');
1395 else
1396 header('Cache-Control: max-age=' . (525600 * 60) . ', private');
1397
1398 if (empty($modSettings['enableCompressedOutput']) || filesize($filename) > 4194304)
1399 header('Content-Length: ' . filesize($filename));
1400
1401 // Try to buy some time...
1402 @set_time_limit(600);
1403
1404 // Recode line endings for text files, if enabled.
1405 if (!empty($modSettings['attachmentRecodeLineEndings']) && !isset($_REQUEST['image']) && in_array($file_ext, array('txt', 'css', 'htm', 'html', 'php', 'xml')))
1406 {
1407 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Windows') !== false)
1408 $callback = create_function('$buffer', 'return preg_replace(\'~[\r]?\n~\', "\r\n", $buffer);');
1409 elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') !== false)
1410 $callback = create_function('$buffer', 'return preg_replace(\'~[\r]?\n~\', "\r", $buffer);');
1411 else
1412 $callback = create_function('$buffer', 'return preg_replace(\'~[\r]?\n~\', "\n", $buffer);');
1413 }
1414
1415 // Since we don't do output compression for files this large...
1416 if (filesize($filename) > 4194304)
1417 {
1418 // Forcibly end any output buffering going on.
1419 if (function_exists('ob_get_level'))
1420 {
1421 while (@ob_get_level() > 0)
1422 @ob_end_clean();
1423 }
1424 else
1425 {
1426 @ob_end_clean();
1427 @ob_end_clean();
1428 @ob_end_clean();
1429 }
1430
1431 $fp = fopen($filename, 'rb');
1432 while (!feof($fp))
1433 {
1434 if (isset($callback))
1435 echo $callback(fread($fp, 8192));
1436 else
1437 echo fread($fp, 8192);
1438 flush();
1439 }
1440 fclose($fp);
1441 }
1442 // 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.
1443 elseif (isset($callback) || @readfile($filename) == null)
1444 echo isset($callback) ? $callback(file_get_contents($filename)) : file_get_contents($filename);
1445
1446 obExit(false);
1447 }
1448
1449 function loadAttachmentContext($id_msg)
1450 {
1451 global $attachments, $modSettings, $txt, $scripturl, $topic, $sourcedir, $smcFunc;
1452
1453 // Set up the attachment info - based on code by Meriadoc.
1454 $attachmentData = array();
1455 $have_unapproved = false;
1456 if (isset($attachments[$id_msg]) && !empty($modSettings['attachmentEnable']))
1457 {
1458 foreach ($attachments[$id_msg] as $i => $attachment)
1459 {
1460 $attachmentData[$i] = array(
1461 'id' => $attachment['id_attach'],
1462 'name' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($attachment['filename'])),
1463 'downloads' => $attachment['downloads'],
1464 'size' => round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'],
1465 'byte_size' => $attachment['filesize'],
1466 'href' => $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_attach'],
1467 'link' => '<a href="' . $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_attach'] . '">' . htmlspecialchars($attachment['filename']) . '</a>',
1468 'is_image' => !empty($attachment['width']) && !empty($attachment['height']) && !empty($modSettings['attachmentShowImages']),
1469 'is_approved' => $attachment['approved'],
1470 );
1471
1472 // If something is unapproved we'll note it so we can sort them.
1473 if (!$attachment['approved'])
1474 $have_unapproved = true;
1475
1476 if (!$attachmentData[$i]['is_image'])
1477 continue;
1478
1479 $attachmentData[$i]['real_width'] = $attachment['width'];
1480 $attachmentData[$i]['width'] = $attachment['width'];
1481 $attachmentData[$i]['real_height'] = $attachment['height'];
1482 $attachmentData[$i]['height'] = $attachment['height'];
1483
1484 // Let's see, do we want thumbs?
1485 if (!empty($modSettings['attachmentThumbnails']) && !empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachment['width'] > $modSettings['attachmentThumbWidth'] || $attachment['height'] > $modSettings['attachmentThumbHeight']) && strlen($attachment['filename']) < 249)
1486 {
1487 // A proper thumb doesn't exist yet? Create one!
1488 if (empty($attachment['id_thumb']) || $attachment['thumb_width'] > $modSettings['attachmentThumbWidth'] || $attachment['thumb_height'] > $modSettings['attachmentThumbHeight'] || ($attachment['thumb_width'] < $modSettings['attachmentThumbWidth'] && $attachment['thumb_height'] < $modSettings['attachmentThumbHeight']))
1489 {
1490 $filename = getAttachmentFilename($attachment['filename'], $attachment['id_attach'], $attachment['id_folder']);
1491
1492 require_once($sourcedir . '/Subs-Graphics.php');
1493 if (createThumbnail($filename, $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
1494 {
1495 // So what folder are we putting this image in?
1496 if (!empty($modSettings['currentAttachmentUploadDir']))
1497 {
1498 if (!is_array($modSettings['attachmentUploadDir']))
1499 $modSettings['attachmentUploadDir'] = @unserialize($modSettings['attachmentUploadDir']);
1500 $path = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
1501 $id_folder_thumb = $modSettings['currentAttachmentUploadDir'];
1502 }
1503 else
1504 {
1505 $path = $modSettings['attachmentUploadDir'];
1506 $id_folder_thumb = 1;
1507 }
1508
1509 // Calculate the size of the created thumbnail.
1510 $size = @getimagesize($filename . '_thumb');
1511 list ($attachment['thumb_width'], $attachment['thumb_height']) = $size;
1512 $thumb_size = filesize($filename . '_thumb');
1513
1514 // These are the only valid image types for SMF.
1515 $validImageTypes = array(1 => 'gif', 2 => 'jpeg', 3 => 'png', 5 => 'psd', 6 => 'bmp', 7 => 'tiff', 8 => 'tiff', 9 => 'jpeg', 14 => 'iff');
1516
1517 // What about the extension?
1518 $thumb_ext = isset($validImageTypes[$size[2]]) ? $validImageTypes[$size[2]] : '';
1519
1520 // Figure out the mime type.
1521 if (!empty($size['mime']))
1522 $thumb_mime = $size['mime'];
1523 else
1524 $thumb_mime = 'image/' . $thumb_ext;
1525
1526 $thumb_filename = $attachment['filename'] . '_thumb';
1527 $thumb_hash = getAttachmentFilename($thumb_filename, false, null, true);
1528
1529 // Add this beauty to the database.
1530 $smcFunc['db_insert']('',
1531 '{db_prefix}attachments',
1532 array('id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string', 'file_hash' => 'string', 'size' => 'int', 'width' => 'int', 'height' => 'int', 'fileext' => 'string', 'mime_type' => 'string'),
1533 array($id_folder_thumb, $id_msg, 3, $thumb_filename, $thumb_hash, (int) $thumb_size, (int) $attachment['thumb_width'], (int) $attachment['thumb_height'], $thumb_ext, $thumb_mime),
1534 array('id_attach')
1535 );
1536 $old_id_thumb = $attachment['id_thumb'];
1537 $attachment['id_thumb'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
1538 if (!empty($attachment['id_thumb']))
1539 {
1540 $smcFunc['db_query']('', '
1541 UPDATE {db_prefix}attachments
1542 SET id_thumb = {int:id_thumb}
1543 WHERE id_attach = {int:id_attach}',
1544 array(
1545 'id_thumb' => $attachment['id_thumb'],
1546 'id_attach' => $attachment['id_attach'],
1547 )
1548 );
1549
1550 $thumb_realname = getAttachmentFilename($thumb_filename, $attachment['id_thumb'], $id_folder_thumb, false, $thumb_hash);
1551 rename($filename . '_thumb', $thumb_realname);
1552
1553 // Do we need to remove an old thumbnail?
1554 if (!empty($old_id_thumb))
1555 {
1556 require_once($sourcedir . '/ManageAttachments.php');
1557 removeAttachments(array('id_attach' => $old_id_thumb), '', false, false);
1558 }
1559 }
1560 }
1561 }
1562
1563 // Only adjust dimensions on successful thumbnail creation.
1564 if (!empty($attachment['thumb_width']) && !empty($attachment['thumb_height']))
1565 {
1566 $attachmentData[$i]['width'] = $attachment['thumb_width'];
1567 $attachmentData[$i]['height'] = $attachment['thumb_height'];
1568 }
1569 }
1570
1571 if (!empty($attachment['id_thumb']))
1572 $attachmentData[$i]['thumbnail'] = array(
1573 'id' => $attachment['id_thumb'],
1574 'href' => $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_thumb'] . ';image',
1575 );
1576 $attachmentData[$i]['thumbnail']['has_thumb'] = !empty($attachment['id_thumb']);
1577
1578 // If thumbnails are disabled, check the maximum size of the image.
1579 if (!$attachmentData[$i]['thumbnail']['has_thumb'] && ((!empty($modSettings['max_image_width']) && $attachment['width'] > $modSettings['max_image_width']) || (!empty($modSettings['max_image_height']) && $attachment['height'] > $modSettings['max_image_height'])))
1580 {
1581 if (!empty($modSettings['max_image_width']) && (empty($modSettings['max_image_height']) || $attachment['height'] * $modSettings['max_image_width'] / $attachment['width'] <= $modSettings['max_image_height']))
1582 {
1583 $attachmentData[$i]['width'] = $modSettings['max_image_width'];
1584 $attachmentData[$i]['height'] = floor($attachment['height'] * $modSettings['max_image_width'] / $attachment['width']);
1585 }
1586 elseif (!empty($modSettings['max_image_width']))
1587 {
1588 $attachmentData[$i]['width'] = floor($attachment['width'] * $modSettings['max_image_height'] / $attachment['height']);
1589 $attachmentData[$i]['height'] = $modSettings['max_image_height'];
1590 }
1591 }
1592 elseif ($attachmentData[$i]['thumbnail']['has_thumb'])
1593 {
1594 // If the image is too large to show inline, make it a popup.
1595 if (((!empty($modSettings['max_image_width']) && $attachmentData[$i]['real_width'] > $modSettings['max_image_width']) || (!empty($modSettings['max_image_height']) && $attachmentData[$i]['real_height'] > $modSettings['max_image_height'])))
1596 $attachmentData[$i]['thumbnail']['javascript'] = 'return reqWin(\'' . $attachmentData[$i]['href'] . ';image\', ' . ($attachment['width'] + 20) . ', ' . ($attachment['height'] + 20) . ', true);';
1597 else
1598 $attachmentData[$i]['thumbnail']['javascript'] = 'return expandThumb(' . $attachment['id_attach'] . ');';
1599 }
1600
1601 if (!$attachmentData[$i]['thumbnail']['has_thumb'])
1602 $attachmentData[$i]['downloads']++;
1603 }
1604 }
1605
1606 // Do we need to instigate a sort?
1607 if ($have_unapproved)
1608 usort($attachmentData, 'approved_attach_sort');
1609
1610 return $attachmentData;
1611 }
1612
1613 // A sort function for putting unapproved attachments first.
1614 function approved_attach_sort($a, $b)
1615 {
1616 if ($a['is_approved'] == $b['is_approved'])
1617 return 0;
1618
1619 return $a['is_approved'] > $b['is_approved'] ? -1 : 1;
1620 }
1621
1622 // In-topic quick moderation.
1623 function QuickInTopicModeration()
1624 {
1625 global $sourcedir, $topic, $board, $user_info, $smcFunc, $modSettings, $context;
1626
1627 // Check the session = get or post.
1628 checkSession('request');
1629
1630 require_once($sourcedir . '/RemoveTopic.php');
1631
1632 if (empty($_REQUEST['msgs']))
1633 redirectexit('topic=' . $topic . '.' . $_REQUEST['start']);
1634
1635 $messages = array();
1636 foreach ($_REQUEST['msgs'] as $dummy)
1637 $messages[] = (int) $dummy;
1638
1639 // We are restoring messages. We handle this in another place.
1640 if (isset($_REQUEST['restore_selected']))
1641 redirectexit('action=restoretopic;msgs=' . implode(',', $messages) . ';' . $context['session_var'] . '=' . $context['session_id']);
1642
1643 // Allowed to delete any message?
1644 if (allowedTo('delete_any'))
1645 $allowed_all = true;
1646 // Allowed to delete replies to their messages?
1647 elseif (allowedTo('delete_replies'))
1648 {
1649 $request = $smcFunc['db_query']('', '
1650 SELECT id_member_started
1651 FROM {db_prefix}topics
1652 WHERE id_topic = {int:current_topic}
1653 LIMIT 1',
1654 array(
1655 'current_topic' => $topic,
1656 )
1657 );
1658 list ($starter) = $smcFunc['db_fetch_row']($request);
1659 $smcFunc['db_free_result']($request);
1660
1661 $allowed_all = $starter == $user_info['id'];
1662 }
1663 else
1664 $allowed_all = false;
1665
1666 // Make sure they're allowed to delete their own messages, if not any.
1667 if (!$allowed_all)
1668 isAllowedTo('delete_own');
1669
1670 // Allowed to remove which messages?
1671 $request = $smcFunc['db_query']('', '
1672 SELECT id_msg, subject, id_member, poster_time
1673 FROM {db_prefix}messages
1674 WHERE id_msg IN ({array_int:message_list})
1675 AND id_topic = {int:current_topic}' . (!$allowed_all ? '
1676 AND id_member = {int:current_member}' : '') . '
1677 LIMIT ' . count($messages),
1678 array(
1679 'current_member' => $user_info['id'],
1680 'current_topic' => $topic,
1681 'message_list' => $messages,
1682 )
1683 );
1684 $messages = array();
1685 while ($row = $smcFunc['db_fetch_assoc']($request))
1686 {
1687 if (!$allowed_all && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time())
1688 continue;
1689
1690 $messages[$row['id_msg']] = array($row['subject'], $row['id_member']);
1691 }
1692 $smcFunc['db_free_result']($request);
1693
1694 // Get the first message in the topic - because you can't delete that!
1695 $request = $smcFunc['db_query']('', '
1696 SELECT id_first_msg, id_last_msg
1697 FROM {db_prefix}topics
1698 WHERE id_topic = {int:current_topic}
1699 LIMIT 1',
1700 array(
1701 'current_topic' => $topic,
1702 )
1703 );
1704 list ($first_message, $last_message) = $smcFunc['db_fetch_row']($request);
1705 $smcFunc['db_free_result']($request);
1706
1707 // Delete all the messages we know they can delete. ($messages)
1708 foreach ($messages as $message => $info)
1709 {
1710 // Just skip the first message - if it's not the last.
1711 if ($message == $first_message && $message != $last_message)
1712 continue;
1713 // If the first message is going then don't bother going back to the topic as we're effectively deleting it.
1714 elseif ($message == $first_message)
1715 $topicGone = true;
1716
1717 removeMessage($message);
1718
1719 // Log this moderation action ;).
1720 if (allowedTo('delete_any') && (!allowedTo('delete_own') || $info[1] != $user_info['id']))
1721 logAction('delete', array('topic' => $topic, 'subject' => $info[0], 'member' => $info[1], 'board' => $board));
1722 }
1723
1724 redirectexit(!empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start']);
1725 }
1726
1727 ?>