Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 1263
n/a
0 / 0
CRAP
n/a
0 / 0
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14/**
15* @ignore
16*/
17define('IN_PHPBB', true);
18$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './';
19$phpEx = substr(strrchr(__FILE__, '.'), 1);
20include($phpbb_root_path . 'common.' . $phpEx);
21include($phpbb_root_path . 'includes/functions_display.' . $phpEx);
22include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
23include($phpbb_root_path . 'includes/functions_user.' . $phpEx);
24
25// Start session management
26$user->session_begin();
27$auth->acl($user->data);
28
29// Initial var setup
30$forum_id    = 0;
31$topic_id    = $request->variable('t', 0);
32$post_id    = $request->variable('p', 0);
33$voted_id    = $request->variable('vote_id', array('' => 0));
34
35$voted_id = (count($voted_id) > 1) ? array_unique($voted_id) : $voted_id;
36
37
38$start        = $request->variable('start', 0);
39$view        = $request->variable('view', '');
40
41$default_sort_days    = (!empty($user->data['user_post_show_days'])) ? $user->data['user_post_show_days'] : 0;
42$default_sort_key    = (!empty($user->data['user_post_sortby_type'])) ? $user->data['user_post_sortby_type'] : 't';
43$default_sort_dir    = (!empty($user->data['user_post_sortby_dir'])) ? $user->data['user_post_sortby_dir'] : 'a';
44
45$sort_days    = $request->variable('st', $default_sort_days);
46$sort_key    = $request->variable('sk', $default_sort_key);
47$sort_dir    = $request->variable('sd', $default_sort_dir);
48
49$update        = $request->variable('update', false);
50
51/* @var $pagination \phpbb\pagination */
52$pagination = $phpbb_container->get('pagination');
53
54$s_can_vote = false;
55/**
56* @todo normalize?
57*/
58$hilit_words    = $request->variable('hilit', '', true);
59
60// Do we have a topic or post id?
61if (!$topic_id && !$post_id)
62{
63    trigger_error('NO_TOPIC');
64}
65
66/* @var $phpbb_content_visibility \phpbb\content_visibility */
67$phpbb_content_visibility = $phpbb_container->get('content.visibility');
68
69// Find topic id if user requested a newer or older topic
70if ($view && !$post_id)
71{
72
73    if ($view == 'unread')
74    {
75        $sql = 'SELECT forum_id
76            FROM ' . TOPICS_TABLE . "
77            WHERE topic_id = $topic_id";
78        $result = $db->sql_query($sql);
79        $forum_id = (int) $db->sql_fetchfield('forum_id');
80        $db->sql_freeresult($result);
81
82        if (!$forum_id)
83        {
84            trigger_error('NO_TOPIC');
85        }
86
87        // Get topic tracking info
88        $topic_tracking_info = get_complete_topic_tracking($forum_id, $topic_id);
89        $topic_last_read = (isset($topic_tracking_info[$topic_id])) ? $topic_tracking_info[$topic_id] : 0;
90
91        $sql = 'SELECT post_id, topic_id, forum_id
92            FROM ' . POSTS_TABLE . "
93            WHERE topic_id = $topic_id
94                AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id) . "
95                AND post_time > $topic_last_read
96                AND forum_id = $forum_id
97            ORDER BY post_time ASC, post_id ASC";
98        $result = $db->sql_query_limit($sql, 1);
99        $row = $db->sql_fetchrow($result);
100        $db->sql_freeresult($result);
101
102        if (!$row)
103        {
104            $sql = 'SELECT topic_last_post_id as post_id, topic_id, forum_id
105                FROM ' . TOPICS_TABLE . '
106                WHERE topic_id = ' . $topic_id;
107            $result = $db->sql_query($sql);
108            $row = $db->sql_fetchrow($result);
109            $db->sql_freeresult($result);
110        }
111
112        if (!$row)
113        {
114            // Setup user environment so we can process lang string
115            $user->setup('viewtopic');
116
117            trigger_error('NO_TOPIC');
118        }
119
120        $post_id = $row['post_id'];
121        $topic_id = $row['topic_id'];
122    }
123    else if ($view == 'next' || $view == 'previous')
124    {
125        $sql_condition = ($view == 'next') ? '>' : '<';
126        $sql_ordering = ($view == 'next') ? 'ASC' : 'DESC';
127
128        $sql = 'SELECT forum_id, topic_last_post_time
129            FROM ' . TOPICS_TABLE . '
130            WHERE topic_id = ' . $topic_id;
131        $result = $db->sql_query($sql);
132        $row = $db->sql_fetchrow($result);
133        $db->sql_freeresult($result);
134
135        if (!$row)
136        {
137            $user->setup('viewtopic');
138            // OK, the topic doesn't exist. This error message is not helpful, but technically correct.
139            trigger_error(($view == 'next') ? 'NO_NEWER_TOPICS' : 'NO_OLDER_TOPICS');
140        }
141        else
142        {
143            $forum_id = $row['forum_id'];
144            $sql = 'SELECT topic_id, forum_id
145                FROM ' . TOPICS_TABLE . '
146                WHERE forum_id = ' . $forum_id . "
147                    AND topic_moved_id = 0
148                    AND topic_last_post_time $sql_condition {$row['topic_last_post_time']}
149                    AND " . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id) . "
150                ORDER BY topic_last_post_time $sql_ordering, topic_last_post_id $sql_ordering";
151            $result = $db->sql_query_limit($sql, 1);
152            $row = $db->sql_fetchrow($result);
153            $db->sql_freeresult($result);
154
155            if (!$row)
156            {
157                $sql = 'SELECT forum_style
158                    FROM ' . FORUMS_TABLE . "
159                    WHERE forum_id = $forum_id";
160                $result = $db->sql_query($sql);
161                $forum_style = (int) $db->sql_fetchfield('forum_style');
162                $db->sql_freeresult($result);
163
164                $user->setup('viewtopic', $forum_style);
165                trigger_error(($view == 'next') ? 'NO_NEWER_TOPICS' : 'NO_OLDER_TOPICS');
166            }
167            else
168            {
169                $topic_id = $row['topic_id'];
170                $forum_id = $row['forum_id'];
171            }
172        }
173    }
174
175    if (isset($row) && $row['forum_id'])
176    {
177        $forum_id = $row['forum_id'];
178    }
179}
180
181// This rather complex gaggle of code handles querying for topics but
182// also allows for direct linking to a post (and the calculation of which
183// page the post is on and the correct display of viewtopic)
184$sql_array = array(
185    'SELECT'    => 't.*, f.*',
186
187    'FROM'        => array(FORUMS_TABLE => 'f'),
188);
189
190// The FROM-Order is quite important here, else t.* columns can not be correctly bound.
191if ($post_id)
192{
193    $sql_array['SELECT'] .= ', p.post_visibility, p.post_time, p.post_id';
194    $sql_array['FROM'][POSTS_TABLE] = 'p';
195}
196
197// Topics table need to be the last in the chain
198$sql_array['FROM'][TOPICS_TABLE] = 't';
199
200if ($user->data['is_registered'])
201{
202    $sql_array['SELECT'] .= ', tw.notify_status';
203    $sql_array['LEFT_JOIN'] = array();
204
205    $sql_array['LEFT_JOIN'][] = array(
206        'FROM'    => array(TOPICS_WATCH_TABLE => 'tw'),
207        'ON'    => 'tw.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = tw.topic_id'
208    );
209
210    if ($config['allow_bookmarks'])
211    {
212        $sql_array['SELECT'] .= ', bm.topic_id as bookmarked';
213        $sql_array['LEFT_JOIN'][] = array(
214            'FROM'    => array(BOOKMARKS_TABLE => 'bm'),
215            'ON'    => 'bm.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = bm.topic_id'
216        );
217    }
218
219    if ($config['load_db_lastread'])
220    {
221        $sql_array['SELECT'] .= ', tt.mark_time, ft.mark_time as forum_mark_time';
222
223        $sql_array['LEFT_JOIN'][] = array(
224            'FROM'    => array(TOPICS_TRACK_TABLE => 'tt'),
225            'ON'    => 'tt.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = tt.topic_id'
226        );
227
228        $sql_array['LEFT_JOIN'][] = array(
229            'FROM'    => array(FORUMS_TRACK_TABLE => 'ft'),
230            'ON'    => 'ft.user_id = ' . $user->data['user_id'] . ' AND t.forum_id = ft.forum_id'
231        );
232    }
233}
234
235if (!$post_id)
236{
237    $sql_array['WHERE'] = "t.topic_id = $topic_id";
238}
239else
240{
241    $sql_array['WHERE'] = "p.post_id = $post_id AND t.topic_id = p.topic_id";
242}
243
244$sql_array['WHERE'] .= ' AND f.forum_id = t.forum_id';
245
246$sql = $db->sql_build_query('SELECT', $sql_array);
247$result = $db->sql_query($sql);
248$topic_data = $db->sql_fetchrow($result);
249$db->sql_freeresult($result);
250
251// link to unapproved post or incorrect link
252if (!$topic_data)
253{
254    // If post_id was submitted, we try at least to display the topic as a last resort...
255    if ($post_id && $topic_id)
256    {
257        redirect(append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id"));
258    }
259
260    trigger_error('NO_TOPIC');
261}
262
263$forum_id = (int) $topic_data['forum_id'];
264
265/**
266 * Modify the forum ID to handle the correct display of viewtopic if needed
267 *
268 * @event core.viewtopic_modify_forum_id
269 * @var string    forum_id        forum ID
270 * @var array    topic_data        array of topic's data
271 * @since 3.2.5-RC1
272 */
273$vars = array(
274    'forum_id',
275    'topic_data',
276);
277extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_forum_id', compact($vars)));
278
279// If the request is missing the f parameter, the forum id in the user session data is 0 at the moment.
280// Let's fix that now so that the user can't hide from the forum's Who Is Online list.
281$user->page['forum'] = $forum_id;
282
283// Now we know the forum_id and can check the permissions
284if (!$phpbb_content_visibility->is_visible('topic', $forum_id, $topic_data))
285{
286    trigger_error('NO_TOPIC');
287}
288
289// This is for determining where we are (page)
290if ($post_id)
291{
292    // are we where we are supposed to be?
293    if (($topic_data['post_visibility'] == ITEM_UNAPPROVED || $topic_data['post_visibility'] == ITEM_REAPPROVE) && !$auth->acl_get('m_approve', $topic_data['forum_id']))
294    {
295        // If post_id was submitted, we try at least to display the topic as a last resort...
296        if ($topic_id)
297        {
298            redirect(append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id"));
299        }
300
301        trigger_error('NO_TOPIC');
302    }
303    if ($post_id == $topic_data['topic_first_post_id'] || $post_id == $topic_data['topic_last_post_id'])
304    {
305        $check_sort = ($post_id == $topic_data['topic_first_post_id']) ? 'd' : 'a';
306
307        if ($sort_dir == $check_sort)
308        {
309            $topic_data['prev_posts'] = $phpbb_content_visibility->get_count('topic_posts', $topic_data, $forum_id) - 1;
310        }
311        else
312        {
313            $topic_data['prev_posts'] = 0;
314        }
315    }
316    else
317    {
318        $sql = 'SELECT COUNT(p.post_id) AS prev_posts
319            FROM ' . POSTS_TABLE . " p
320            WHERE p.topic_id = {$topic_data['topic_id']}
321                AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id, 'p.');
322
323        if ($sort_dir == 'd')
324        {
325            $sql .= " AND (p.post_time > {$topic_data['post_time']} OR (p.post_time = {$topic_data['post_time']} AND p.post_id >= {$topic_data['post_id']}))";
326        }
327        else
328        {
329            $sql .= " AND (p.post_time < {$topic_data['post_time']} OR (p.post_time = {$topic_data['post_time']} AND p.post_id <= {$topic_data['post_id']}))";
330        }
331
332        $result = $db->sql_query($sql);
333        $row = $db->sql_fetchrow($result);
334        $db->sql_freeresult($result);
335
336        $topic_data['prev_posts'] = $row['prev_posts'] - 1;
337    }
338}
339
340$topic_id = (int) $topic_data['topic_id'];
341$topic_replies = $phpbb_content_visibility->get_count('topic_posts', $topic_data, $forum_id) - 1;
342
343// Check sticky/announcement/global  time limit
344if (($topic_data['topic_type'] != POST_NORMAL) && $topic_data['topic_time_limit'] && ($topic_data['topic_time'] + $topic_data['topic_time_limit']) < time())
345{
346    $sql = 'UPDATE ' . TOPICS_TABLE . '
347        SET topic_type = ' . POST_NORMAL . ', topic_time_limit = 0
348        WHERE topic_id = ' . $topic_id;
349    $db->sql_query($sql);
350
351    $topic_data['topic_type'] = POST_NORMAL;
352    $topic_data['topic_time_limit'] = 0;
353}
354
355// Setup look and feel
356$user->setup('viewtopic', $topic_data['forum_style']);
357
358if ($view == 'print' && !$auth->acl_get('f_print', $forum_id))
359{
360    send_status_line(403, 'Forbidden');
361    trigger_error('NO_AUTH_PRINT_TOPIC');
362}
363
364$overrides_f_read_check = false;
365$overrides_forum_password_check = false;
366$topic_tracking_info = isset($topic_tracking_info) ? $topic_tracking_info : null;
367
368/**
369* Event to apply extra permissions and to override original phpBB's f_read permission and forum password check
370* on viewtopic access
371*
372* @event core.viewtopic_before_f_read_check
373* @var    int        forum_id                        The forum id from where the topic belongs
374* @var    int        topic_id                        The id of the topic the user tries to access
375* @var    int        post_id                            The id of the post the user tries to start viewing at.
376*                                                It may be 0 for none given.
377* @var    array    topic_data                        All the information from the topic and forum tables for this topic
378*                                                 It includes posts information if post_id is not 0
379* @var    bool    overrides_f_read_check            Set true to remove f_read check afterwards
380* @var    bool    overrides_forum_password_check    Set true to remove forum_password check afterwards
381* @var    array    topic_tracking_info                Information upon calling get_topic_tracking()
382*                                                Set it to NULL to allow auto-filling later.
383*                                                Set it to an array to override original data.
384* @since 3.1.3-RC1
385*/
386$vars = array(
387    'forum_id',
388    'topic_id',
389    'post_id',
390    'topic_data',
391    'overrides_f_read_check',
392    'overrides_forum_password_check',
393    'topic_tracking_info',
394);
395extract($phpbb_dispatcher->trigger_event('core.viewtopic_before_f_read_check', compact($vars)));
396
397// Start auth check
398if (!$overrides_f_read_check && !$auth->acl_get('f_read', $forum_id))
399{
400    if ($user->data['user_id'] != ANONYMOUS)
401    {
402        send_status_line(403, 'Forbidden');
403        trigger_error('SORRY_AUTH_READ');
404    }
405
406    login_box('', $user->lang['LOGIN_VIEWFORUM']);
407}
408
409// Forum is passworded ... check whether access has been granted to this
410// user this session, if not show login box
411if (!$overrides_forum_password_check && $topic_data['forum_password'])
412{
413    login_forum_box($topic_data);
414}
415
416// Redirect to login upon emailed notification links if user is not logged in.
417if (isset($_GET['e']) && $user->data['user_id'] == ANONYMOUS)
418{
419    login_box(build_url('e') . '#unread', $user->lang['LOGIN_NOTIFY_TOPIC']);
420}
421
422// What is start equal to?
423if ($post_id)
424{
425    $start = floor(($topic_data['prev_posts']) / $config['posts_per_page']) * $config['posts_per_page'];
426}
427
428// Get topic tracking info
429if (!isset($topic_tracking_info))
430{
431    $topic_tracking_info = array();
432
433    // Get topic tracking info
434    if ($config['load_db_lastread'] && $user->data['is_registered'])
435    {
436        $tmp_topic_data = array($topic_id => $topic_data);
437        $topic_tracking_info = get_topic_tracking($forum_id, $topic_id, $tmp_topic_data, array($forum_id => $topic_data['forum_mark_time']));
438        unset($tmp_topic_data);
439    }
440    else if ($config['load_anon_lastread'] || $user->data['is_registered'])
441    {
442        $topic_tracking_info = get_complete_topic_tracking($forum_id, $topic_id);
443    }
444}
445
446// Post ordering options
447$limit_days = array(0 => $user->lang['ALL_POSTS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']);
448
449$sort_by_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 's' => $user->lang['SUBJECT']);
450$sort_by_sql = array('a' => array('u.username_clean', 'p.post_id'), 't' => array('p.post_time', 'p.post_id'), 's' => array('p.post_subject', 'p.post_id'));
451$join_user_sql = array('a' => true, 't' => false, 's' => false);
452
453$s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = '';
454
455/**
456* Event to add new sorting options
457*
458* @event core.viewtopic_gen_sort_selects_before
459* @var    array    limit_days        Limit results by time
460* @var    array    sort_by_text    Language strings for sorting options
461* @var    array    sort_by_sql        SQL conditions for sorting options
462* @var    array    join_user_sql    SQL joins required for sorting options
463* @var    int        sort_days        User selected sort days
464* @var    string    sort_key        User selected sort key
465* @var    string    sort_dir        User selected sort direction
466* @var    string    s_limit_days    Initial value of limit days selectbox
467* @var    string    s_sort_key        Initial value of sort key selectbox
468* @var    string    s_sort_dir        Initial value of sort direction selectbox
469* @var    string    u_sort_param    Initial value of sorting form action
470* @since 3.2.8-RC1
471*/
472$vars = array(
473    'limit_days',
474    'sort_by_text',
475    'sort_by_sql',
476    'join_user_sql',
477    'sort_days',
478    'sort_key',
479    'sort_dir',
480    's_limit_days',
481    's_sort_key',
482    's_sort_dir',
483    'u_sort_param',
484);
485extract($phpbb_dispatcher->trigger_event('core.viewtopic_gen_sort_selects_before', compact($vars)));
486
487gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param, $default_sort_days, $default_sort_key, $default_sort_dir);
488
489// Obtain correct post count and ordering SQL if user has
490// requested anything different
491if ($sort_days)
492{
493    $min_post_time = time() - ($sort_days * 86400);
494
495    $sql = 'SELECT COUNT(post_id) AS num_posts
496        FROM ' . POSTS_TABLE . "
497        WHERE topic_id = $topic_id
498            AND post_time >= $min_post_time
499                AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id);
500    $result = $db->sql_query($sql);
501    $total_posts = (int) $db->sql_fetchfield('num_posts');
502    $db->sql_freeresult($result);
503
504    $limit_posts_time = "AND p.post_time >= $min_post_time ";
505
506    if (isset($_POST['sort']))
507    {
508        $start = 0;
509    }
510}
511else
512{
513    $total_posts = $topic_replies + 1;
514    $limit_posts_time = '';
515}
516
517// Was a highlight request part of the URI?
518$highlight_match = $highlight = '';
519if ($hilit_words)
520{
521    $highlight_match = phpbb_clean_search_string($hilit_words);
522    $highlight = urlencode($highlight_match);
523    $highlight_match = str_replace('\*', '\w+?', preg_quote($highlight_match, '#'));
524    $highlight_match = preg_replace('#(?<=^|\s)\\\\w\*\?(?=\s|$)#', '\w+?', $highlight_match);
525    $highlight_match = str_replace(' ', '|', $highlight_match);
526}
527
528// Make sure $start is set to the last page if it exceeds the amount
529$start = $pagination->validate_start($start, $config['posts_per_page'], $total_posts);
530
531// General Viewtopic URL for return links
532$viewtopic_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&amp;start=$start") . ((strlen($u_sort_param)) ? "&amp;$u_sort_param" : '') . (($highlight_match) ? "&amp;hilit=$highlight" : ''));
533
534// Are we watching this topic?
535$s_watching_topic = array(
536    'link'            => '',
537    'link_toggle'    => '',
538    'title'            => '',
539    'title_toggle'    => '',
540    'is_watching'    => false,
541);
542
543if ($config['allow_topic_notify'])
544{
545    $notify_status = (isset($topic_data['notify_status'])) ? $topic_data['notify_status'] : null;
546    watch_topic_forum('topic', $s_watching_topic, $user->data['user_id'], $forum_id, $topic_id, $notify_status, $start, $topic_data['topic_title']);
547
548    // Reset forum notification if forum notify is set
549    if ($config['allow_forum_notify'] && $auth->acl_get('f_subscribe', $forum_id))
550    {
551        $s_watching_forum = $s_watching_topic;
552        watch_topic_forum('forum', $s_watching_forum, $user->data['user_id'], $forum_id, 0);
553    }
554}
555
556/**
557* Event to modify highlight.
558*
559* @event core.viewtopic_highlight_modify
560* @var    string    highlight            String to be highlighted
561* @var    string    highlight_match        Highlight string to be used in preg_replace
562* @var    array    topic_data            Topic data
563* @var    int        start                Pagination start
564* @var    int        total_posts            Number of posts
565* @var    string    viewtopic_url        Current viewtopic URL
566* @since 3.1.11-RC1
567*/
568$vars = array(
569    'highlight',
570    'highlight_match',
571    'topic_data',
572    'start',
573    'total_posts',
574    'viewtopic_url',
575);
576extract($phpbb_dispatcher->trigger_event('core.viewtopic_highlight_modify', compact($vars)));
577
578// Bookmarks
579if ($config['allow_bookmarks'] && $user->data['is_registered'] && $request->variable('bookmark', 0))
580{
581    if (check_link_hash($request->variable('hash', ''), "topic_$topic_id"))
582    {
583        if (!$topic_data['bookmarked'])
584        {
585            $sql = 'INSERT INTO ' . BOOKMARKS_TABLE . ' ' . $db->sql_build_array('INSERT', array(
586                'user_id'    => $user->data['user_id'],
587                'topic_id'    => $topic_id,
588            ));
589            $db->sql_query($sql);
590        }
591        else
592        {
593            $sql = 'DELETE FROM ' . BOOKMARKS_TABLE . "
594                WHERE user_id = {$user->data['user_id']}
595                    AND topic_id = $topic_id";
596            $db->sql_query($sql);
597        }
598        $message = (($topic_data['bookmarked']) ? $user->lang['BOOKMARK_REMOVED'] : $user->lang['BOOKMARK_ADDED']);
599
600        if (!$request->is_ajax())
601        {
602            $message .= '<br /><br />' . $user->lang('RETURN_TOPIC', '<a href="' . $viewtopic_url . '">', '</a>');
603        }
604    }
605    else
606    {
607        $message = $user->lang['BOOKMARK_ERR'];
608
609        if (!$request->is_ajax())
610        {
611            $message .= '<br /><br />' . $user->lang('RETURN_TOPIC', '<a href="' . $viewtopic_url . '">', '</a>');
612        }
613    }
614    meta_refresh(3, $viewtopic_url);
615
616    trigger_error($message);
617}
618
619// Grab ranks
620$ranks = $cache->obtain_ranks();
621
622// Grab icons
623$icons = $cache->obtain_icons();
624
625// Grab extensions
626$extensions = array();
627if ($topic_data['topic_attachment'])
628{
629    $extensions = $cache->obtain_attach_extensions($forum_id);
630}
631
632// Forum rules listing
633$s_forum_rules = '';
634gen_forum_auth_level('topic', $forum_id, $topic_data['forum_status']);
635
636// Quick mod tools
637$allow_change_type = ($auth->acl_get('m_', $forum_id) || ($user->data['is_registered'] && $user->data['user_id'] == $topic_data['topic_poster'])) ? true : false;
638
639$s_quickmod_action = append_sid(
640    "{$phpbb_root_path}mcp.$phpEx",
641    array(
642        'f'    => $forum_id,
643        't'    => $topic_id,
644        'start'        => $start,
645        'quickmod'    => 1,
646        'redirect'    => urlencode(str_replace('&amp;', '&', $viewtopic_url)),
647    )
648);
649
650$quickmod_array = array(
651//    'key'            => array('LANG_KEY', $userHasPermissions),
652
653    'lock'                    => array('LOCK_TOPIC', ($topic_data['topic_status'] == ITEM_UNLOCKED) && ($auth->acl_get('m_lock', $forum_id) || ($auth->acl_get('f_user_lock', $forum_id) && $user->data['is_registered'] && $user->data['user_id'] == $topic_data['topic_poster']))),
654    'unlock'                => array('UNLOCK_TOPIC', ($topic_data['topic_status'] != ITEM_UNLOCKED) && ($auth->acl_get('m_lock', $forum_id))),
655    'delete_topic'        => array('DELETE_TOPIC', ($auth->acl_get('m_delete', $forum_id) || (($topic_data['topic_visibility'] != ITEM_DELETED) && $auth->acl_get('m_softdelete', $forum_id)))),
656    'restore_topic'        => array('RESTORE_TOPIC', (($topic_data['topic_visibility'] == ITEM_DELETED) && $auth->acl_get('m_approve', $forum_id))),
657    'move'                    => array('MOVE_TOPIC', $auth->acl_get('m_move', $forum_id) && $topic_data['topic_status'] != ITEM_MOVED),
658    'split'                    => array('SPLIT_TOPIC', $auth->acl_get('m_split', $forum_id)),
659    'merge'                    => array('MERGE_POSTS', $auth->acl_get('m_merge', $forum_id)),
660    'merge_topic'        => array('MERGE_TOPIC', $auth->acl_get('m_merge', $forum_id)),
661    'fork'                    => array('FORK_TOPIC', $auth->acl_get('m_move', $forum_id)),
662    'make_normal'        => array('MAKE_NORMAL', ($allow_change_type && $auth->acl_gets('f_sticky', 'f_announce', 'f_announce_global', $forum_id) && $topic_data['topic_type'] != POST_NORMAL)),
663    'make_sticky'        => array('MAKE_STICKY', ($allow_change_type && $auth->acl_get('f_sticky', $forum_id) && $topic_data['topic_type'] != POST_STICKY)),
664    'make_announce'    => array('MAKE_ANNOUNCE', ($allow_change_type && $auth->acl_get('f_announce', $forum_id) && $topic_data['topic_type'] != POST_ANNOUNCE)),
665    'make_global'        => array('MAKE_GLOBAL', ($allow_change_type && $auth->acl_get('f_announce_global', $forum_id) && $topic_data['topic_type'] != POST_GLOBAL)),
666    'topic_logs'            => array('VIEW_TOPIC_LOGS', $auth->acl_get('m_', $forum_id)),
667);
668
669/**
670* Event to modify data in the quickmod_array before it gets sent to the
671* phpbb_add_quickmod_option function.
672*
673* @event core.viewtopic_add_quickmod_option_before
674* @var    int                forum_id                Forum ID
675* @var    int                post_id                    Post ID
676* @var    array            quickmod_array            Array with quick moderation options data
677* @var    array            topic_data                Array with topic data
678* @var    int                topic_id                Topic ID
679* @var    array            topic_tracking_info        Array with topic tracking data
680* @var    string            viewtopic_url            URL to the topic page
681* @var    bool            allow_change_type        Topic change permissions check
682* @since 3.1.9-RC1
683*/
684$vars = array(
685    'forum_id',
686    'post_id',
687    'quickmod_array',
688    'topic_data',
689    'topic_id',
690    'topic_tracking_info',
691    'viewtopic_url',
692    'allow_change_type',
693);
694extract($phpbb_dispatcher->trigger_event('core.viewtopic_add_quickmod_option_before', compact($vars)));
695
696foreach ($quickmod_array as $option => $qm_ary)
697{
698    if (!empty($qm_ary[1]))
699    {
700        phpbb_add_quickmod_option($s_quickmod_action, $option, $qm_ary[0]);
701    }
702}
703
704// Navigation links
705generate_forum_nav($topic_data);
706
707// Forum Rules
708generate_forum_rules($topic_data);
709
710// Moderators
711$forum_moderators = array();
712if ($config['load_moderators'])
713{
714    get_moderators($forum_moderators, $forum_id);
715}
716
717// This is only used for print view so ...
718$server_path = (!$view) ? $phpbb_root_path : generate_board_url() . '/';
719
720// Replace naughty words in title
721$topic_data['topic_title'] = censor_text($topic_data['topic_title']);
722
723$s_search_hidden_fields = array(
724    't' => $topic_id,
725    'sf' => 'msgonly',
726);
727if ($_SID)
728{
729    $s_search_hidden_fields['sid'] = $_SID;
730}
731
732if (!empty($_EXTRA_URL))
733{
734    foreach ($_EXTRA_URL as $url_param)
735    {
736        $url_param = explode('=', $url_param, 2);
737        $s_search_hidden_fields[$url_param[0]] = $url_param[1];
738    }
739}
740
741// If we've got a hightlight set pass it on to pagination.
742$base_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . ((strlen($u_sort_param)) ? "&amp;$u_sort_param" : '') . (($highlight_match) ? "&amp;hilit=$highlight" : ''));
743
744/**
745* Event to modify data before template variables are being assigned
746*
747* @event core.viewtopic_assign_template_vars_before
748* @var    string    base_url            URL to be passed to generate pagination
749* @var    int        forum_id            Forum ID
750* @var    int        post_id                Post ID
751* @var    array    quickmod_array        Array with quick moderation options data
752* @var    int        start                Pagination information
753* @var    array    topic_data            Array with topic data
754* @var    int        topic_id            Topic ID
755* @var    array    topic_tracking_info    Array with topic tracking data
756* @var    int        total_posts            Topic total posts count
757* @var    string    viewtopic_url        URL to the topic page
758* @since 3.1.0-RC4
759* @changed 3.1.2-RC1 Added viewtopic_url
760*/
761$vars = array(
762    'base_url',
763    'forum_id',
764    'post_id',
765    'quickmod_array',
766    'start',
767    'topic_data',
768    'topic_id',
769    'topic_tracking_info',
770    'total_posts',
771    'viewtopic_url',
772);
773extract($phpbb_dispatcher->trigger_event('core.viewtopic_assign_template_vars_before', compact($vars)));
774
775$pagination->generate_template_pagination($base_url, 'pagination', 'start', $total_posts, $config['posts_per_page'], $start);
776
777// Send vars to template
778$template->assign_vars(array(
779    'FORUM_ID'         => $forum_id,
780    'FORUM_NAME'     => $topic_data['forum_name'],
781    'FORUM_DESC'    => generate_text_for_display($topic_data['forum_desc'], $topic_data['forum_desc_uid'], $topic_data['forum_desc_bitfield'], $topic_data['forum_desc_options']),
782    'TOPIC_ID'         => $topic_id,
783    'TOPIC_TITLE'     => $topic_data['topic_title'],
784    'TOPIC_POSTER'    => $topic_data['topic_poster'],
785
786    'TOPIC_AUTHOR_FULL'        => get_username_string('full', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']),
787    'TOPIC_AUTHOR_COLOUR'    => get_username_string('colour', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']),
788    'TOPIC_AUTHOR'            => get_username_string('username', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']),
789
790    'TOTAL_POSTS'    => $user->lang('VIEW_TOPIC_POSTS', (int) $total_posts),
791    'U_MCP'         => ($auth->acl_get('m_', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&amp;mode=topic_view&amp;t=$topic_id" . (($start == 0) ? '' : "&amp;start=$start") . ((strlen($u_sort_param)) ? "&amp;$u_sort_param" : '')) : '',
792    'MODERATORS'    => (isset($forum_moderators[$forum_id]) && count($forum_moderators[$forum_id])) ? implode($user->lang['COMMA_SEPARATOR'], $forum_moderators[$forum_id]) : '',
793
794    'POST_IMG'             => ($topic_data['forum_status'] == ITEM_LOCKED) ? $user->img('button_topic_locked', 'FORUM_LOCKED') : $user->img('button_topic_new', 'POST_NEW_TOPIC'),
795    'QUOTE_IMG'         => $user->img('icon_post_quote', 'REPLY_WITH_QUOTE'),
796    'REPLY_IMG'            => ($topic_data['forum_status'] == ITEM_LOCKED || $topic_data['topic_status'] == ITEM_LOCKED) ? $user->img('button_topic_locked', 'TOPIC_LOCKED') : $user->img('button_topic_reply', 'REPLY_TO_TOPIC'),
797    'EDIT_IMG'             => $user->img('icon_post_edit', 'EDIT_POST'),
798    'DELETE_IMG'         => $user->img('icon_post_delete', 'DELETE_POST'),
799    'DELETED_IMG'        => $user->img('icon_topic_deleted', 'POST_DELETED_RESTORE'),
800    'INFO_IMG'             => $user->img('icon_post_info', 'VIEW_INFO'),
801    'PROFILE_IMG'        => $user->img('icon_user_profile', 'READ_PROFILE'),
802    'SEARCH_IMG'         => $user->img('icon_user_search', 'SEARCH_USER_POSTS'),
803    'PM_IMG'             => $user->img('icon_contact_pm', 'SEND_PRIVATE_MESSAGE'),
804    'EMAIL_IMG'         => $user->img('icon_contact_email', 'SEND_EMAIL'),
805    'REPORT_IMG'        => $user->img('icon_post_report', 'REPORT_POST'),
806    'REPORTED_IMG'        => $user->img('icon_topic_reported', 'POST_REPORTED'),
807    'UNAPPROVED_IMG'    => $user->img('icon_topic_unapproved', 'POST_UNAPPROVED'),
808    'WARN_IMG'            => $user->img('icon_user_warn', 'WARN_USER'),
809
810    'S_IS_LOCKED'            => ($topic_data['topic_status'] == ITEM_UNLOCKED && $topic_data['forum_status'] == ITEM_UNLOCKED) ? false : true,
811    'S_SELECT_SORT_DIR'     => $s_sort_dir,
812    'S_SELECT_SORT_KEY'     => $s_sort_key,
813    'S_SELECT_SORT_DAYS'     => $s_limit_days,
814    'S_SINGLE_MODERATOR'    => (!empty($forum_moderators[$forum_id]) && count($forum_moderators[$forum_id]) > 1) ? false : true,
815    'S_TOPIC_ACTION'         => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&amp;start=$start")),
816    'S_MOD_ACTION'             => $s_quickmod_action,
817
818    'L_RETURN_TO_FORUM'        => $user->lang('RETURN_TO', $topic_data['forum_name']),
819    'S_VIEWTOPIC'            => true,
820    'S_UNREAD_VIEW'            => $view == 'unread',
821    'S_DISPLAY_SEARCHBOX'    => ($auth->acl_get('u_search') && $auth->acl_get('f_search', $forum_id) && $config['load_search']) ? true : false,
822    'S_SEARCHBOX_ACTION'    => append_sid("{$phpbb_root_path}search.$phpEx"),
823    'S_SEARCH_LOCAL_HIDDEN_FIELDS'    => build_hidden_fields($s_search_hidden_fields),
824
825    'S_DISPLAY_POST_INFO'    => ($topic_data['forum_type'] == FORUM_POST && ($auth->acl_get('f_post', $forum_id) || $user->data['user_id'] == ANONYMOUS)) ? true : false,
826    'S_DISPLAY_REPLY_INFO'    => ($topic_data['forum_type'] == FORUM_POST && ($auth->acl_get('f_reply', $forum_id) || $user->data['user_id'] == ANONYMOUS)) ? true : false,
827    'S_ENABLE_FEEDS_TOPIC'    => ($config['feed_topic'] && !phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $topic_data['forum_options'])) ? true : false,
828
829    'U_TOPIC'                => "{$server_path}viewtopic.$phpEx?t=$topic_id",
830    'U_FORUM'                => $server_path,
831    'U_VIEW_TOPIC'             => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&amp;start=$start") . (strlen($u_sort_param) ? "&amp;$u_sort_param" : '')),
832    'U_CANONICAL'            => generate_board_url() . '/' . append_sid("viewtopic.$phpEx", "t=$topic_id" . (($start) ? "&amp;start=$start" : ''), true, ''),
833    'U_VIEW_FORUM'             => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id),
834    'U_VIEW_OLDER_TOPIC'    => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id&amp;view=previous"),
835    'U_VIEW_NEWER_TOPIC'    => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id&amp;view=next"),
836    'U_PRINT_TOPIC'            => ($auth->acl_get('f_print', $forum_id)) ? $viewtopic_url . '&amp;view=print' : '',
837    'U_EMAIL_TOPIC'            => ($auth->acl_get('f_email', $forum_id) && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=email&amp;t=$topic_id") : '',
838
839    'U_WATCH_TOPIC'            => $s_watching_topic['link'],
840    'U_WATCH_TOPIC_TOGGLE'    => $s_watching_topic['link_toggle'],
841    'S_WATCH_TOPIC_TITLE'    => $s_watching_topic['title'],
842    'S_WATCH_TOPIC_TOGGLE'    => $s_watching_topic['title_toggle'],
843    'S_WATCHING_TOPIC'        => $s_watching_topic['is_watching'],
844
845    'U_BOOKMARK_TOPIC'        => ($user->data['is_registered'] && $config['allow_bookmarks']) ? $viewtopic_url . '&amp;bookmark=1&amp;hash=' . generate_link_hash("topic_$topic_id") : '',
846    'S_BOOKMARK_TOPIC'        => ($user->data['is_registered'] && $config['allow_bookmarks'] && $topic_data['bookmarked']) ? $user->lang['BOOKMARK_TOPIC_REMOVE'] : $user->lang['BOOKMARK_TOPIC'],
847    'S_BOOKMARK_TOGGLE'        => (!$user->data['is_registered'] || !$config['allow_bookmarks'] || !$topic_data['bookmarked']) ? $user->lang['BOOKMARK_TOPIC_REMOVE'] : $user->lang['BOOKMARK_TOPIC'],
848    'S_BOOKMARKED_TOPIC'    => ($user->data['is_registered'] && $config['allow_bookmarks'] && $topic_data['bookmarked']) ? true : false,
849
850    'U_POST_NEW_TOPIC'         => ($auth->acl_get('f_post', $forum_id) || $user->data['user_id'] == ANONYMOUS) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=post&amp;f=$forum_id") : '',
851    'U_POST_REPLY_TOPIC'     => ($auth->acl_get('f_reply', $forum_id) || $user->data['user_id'] == ANONYMOUS) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=reply&amp;t=$topic_id") : '',
852    'U_BUMP_TOPIC'            => (bump_topic_allowed($forum_id, $topic_data['topic_bumped'], $topic_data['topic_last_post_time'], $topic_data['topic_poster'], $topic_data['topic_last_poster_id'])) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=bump&amp;t=$topic_id&amp;hash=" . generate_link_hash("topic_$topic_id")) : '')
853);
854
855// Does this topic contain a poll?
856if (!empty($topic_data['poll_start']))
857{
858    $sql = 'SELECT o.*, p.bbcode_bitfield, p.bbcode_uid
859        FROM ' . POLL_OPTIONS_TABLE . ' o, ' . POSTS_TABLE . " p
860        WHERE o.topic_id = $topic_id
861            AND p.post_id = {$topic_data['topic_first_post_id']}
862            AND p.topic_id = o.topic_id
863        ORDER BY o.poll_option_id";
864    $result = $db->sql_query($sql);
865
866    $poll_info = $vote_counts = array();
867    while ($row = $db->sql_fetchrow($result))
868    {
869        $poll_info[] = $row;
870        $option_id = (int) $row['poll_option_id'];
871        $vote_counts[$option_id] = (int) $row['poll_option_total'];
872    }
873    $db->sql_freeresult($result);
874
875    $cur_voted_id = array();
876    if ($user->data['is_registered'])
877    {
878        $sql = 'SELECT poll_option_id
879            FROM ' . POLL_VOTES_TABLE . '
880            WHERE topic_id = ' . $topic_id . '
881                AND vote_user_id = ' . $user->data['user_id'];
882        $result = $db->sql_query($sql);
883
884        while ($row = $db->sql_fetchrow($result))
885        {
886            $cur_voted_id[] = $row['poll_option_id'];
887        }
888        $db->sql_freeresult($result);
889    }
890    else
891    {
892        // Cookie based guest tracking ... I don't like this but hum ho
893        // it's oft requested. This relies on "nice" users who don't feel
894        // the need to delete cookies to mess with results.
895        if ($request->is_set($config['cookie_name'] . '_poll_' . $topic_id, \phpbb\request\request_interface::COOKIE))
896        {
897            $cur_voted_id = explode(',', $request->variable($config['cookie_name'] . '_poll_' . $topic_id, '', true, \phpbb\request\request_interface::COOKIE));
898            $cur_voted_id = array_map('intval', $cur_voted_id);
899        }
900    }
901
902    // Can not vote at all if no vote permission
903    $s_can_vote = ($auth->acl_get('f_vote', $forum_id) &&
904        (($topic_data['poll_length'] != 0 && $topic_data['poll_start'] + $topic_data['poll_length'] > time()) || $topic_data['poll_length'] == 0) &&
905        $topic_data['topic_status'] != ITEM_LOCKED &&
906        $topic_data['forum_status'] != ITEM_LOCKED &&
907        (!count($cur_voted_id) ||
908        ($auth->acl_get('f_votechg', $forum_id) && $topic_data['poll_vote_change']))) ? true : false;
909    $s_display_results = (!$s_can_vote || ($s_can_vote && count($cur_voted_id)) || $view == 'viewpoll') ? true : false;
910
911    /**
912    * Event to manipulate the poll data
913    *
914    * @event core.viewtopic_modify_poll_data
915    * @var    array    cur_voted_id                Array with options' IDs current user has voted for
916    * @var    int        forum_id                    The topic's forum id
917    * @var    array    poll_info                    Array with the poll information
918    * @var    bool    s_can_vote                    Flag indicating if a user can vote
919    * @var    bool    s_display_results            Flag indicating if results or poll options should be displayed
920    * @var    int        topic_id                    The id of the topic the user tries to access
921    * @var    array    topic_data                    All the information from the topic and forum tables for this topic
922    * @var    string    viewtopic_url                URL to the topic page
923    * @var    array    vote_counts                    Array with the vote counts for every poll option
924    * @var    array    voted_id                    Array with updated options' IDs current user is voting for
925    * @since 3.1.5-RC1
926    */
927    $vars = array(
928        'cur_voted_id',
929        'forum_id',
930        'poll_info',
931        's_can_vote',
932        's_display_results',
933        'topic_id',
934        'topic_data',
935        'viewtopic_url',
936        'vote_counts',
937        'voted_id',
938    );
939    extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_poll_data', compact($vars)));
940
941    if ($update && $s_can_vote)
942    {
943
944        if (!count($voted_id) || count($voted_id) > $topic_data['poll_max_options'] || in_array(VOTE_CONVERTED, $cur_voted_id) || !check_form_key('posting'))
945        {
946            $redirect_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&amp;start=$start"));
947
948            meta_refresh(5, $redirect_url);
949            if (!count($voted_id))
950            {
951                $message = 'NO_VOTE_OPTION';
952            }
953            else if (count($voted_id) > $topic_data['poll_max_options'])
954            {
955                $message = 'TOO_MANY_VOTE_OPTIONS';
956            }
957            else if (in_array(VOTE_CONVERTED, $cur_voted_id))
958            {
959                $message = 'VOTE_CONVERTED';
960            }
961            else
962            {
963                $message = 'FORM_INVALID';
964            }
965
966            $message = $user->lang[$message] . '<br /><br />' . sprintf($user->lang['RETURN_TOPIC'], '<a href="' . $redirect_url . '">', '</a>');
967            trigger_error($message);
968        }
969
970        foreach ($voted_id as $option)
971        {
972            if (in_array($option, $cur_voted_id))
973            {
974                continue;
975            }
976
977            $sql = 'UPDATE ' . POLL_OPTIONS_TABLE . '
978                SET poll_option_total = poll_option_total + 1
979                WHERE poll_option_id = ' . (int) $option . '
980                    AND topic_id = ' . (int) $topic_id;
981            $db->sql_query($sql);
982
983            $vote_counts[$option]++;
984
985            if ($user->data['is_registered'])
986            {
987                $sql_ary = array(
988                    'topic_id'            => (int) $topic_id,
989                    'poll_option_id'    => (int) $option,
990                    'vote_user_id'        => (int) $user->data['user_id'],
991                    'vote_user_ip'        => (string) $user->ip,
992                );
993
994                $sql = 'INSERT INTO ' . POLL_VOTES_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
995                $db->sql_query($sql);
996            }
997        }
998
999        foreach ($cur_voted_id as $option)
1000        {
1001            if (!in_array($option, $voted_id))
1002            {
1003                $sql = 'UPDATE ' . POLL_OPTIONS_TABLE . '
1004                    SET poll_option_total = poll_option_total - 1
1005                    WHERE poll_option_id = ' . (int) $option . '
1006                        AND topic_id = ' . (int) $topic_id;
1007                $db->sql_query($sql);
1008
1009                $vote_counts[$option]--;
1010
1011                if ($user->data['is_registered'])
1012                {
1013                    $sql = 'DELETE FROM ' . POLL_VOTES_TABLE . '
1014                        WHERE topic_id = ' . (int) $topic_id . '
1015                            AND poll_option_id = ' . (int) $option . '
1016                            AND vote_user_id = ' . (int) $user->data['user_id'];
1017                    $db->sql_query($sql);
1018                }
1019            }
1020        }
1021
1022        if ($user->data['user_id'] == ANONYMOUS && !$user->data['is_bot'])
1023        {
1024            $user->set_cookie('poll_' . $topic_id, implode(',', $voted_id), time() + 31536000);
1025        }
1026
1027        $sql = 'UPDATE ' . TOPICS_TABLE . '
1028            SET poll_last_vote = ' . time() . "
1029            WHERE topic_id = $topic_id";
1030        //, topic_last_post_time = ' . time() . " -- for bumping topics with new votes, ignore for now
1031        $db->sql_query($sql);
1032
1033        $redirect_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&amp;start=$start"));
1034        $message = $user->lang['VOTE_SUBMITTED'] . '<br /><br />' . sprintf($user->lang['RETURN_TOPIC'], '<a href="' . $redirect_url . '">', '</a>');
1035
1036        if ($request->is_ajax())
1037        {
1038            // Filter out invalid options
1039            $valid_user_votes = array_intersect(array_keys($vote_counts), $voted_id);
1040
1041            $data = array(
1042                'NO_VOTES'            => $user->lang['NO_VOTES'],
1043                'success'            => true,
1044                'user_votes'        => array_flip($valid_user_votes),
1045                'vote_counts'        => $vote_counts,
1046                'total_votes'        => array_sum($vote_counts),
1047                'can_vote'            => !count($valid_user_votes) || ($auth->acl_get('f_votechg', $forum_id) && $topic_data['poll_vote_change']),
1048            );
1049
1050            /**
1051            * Event to manipulate the poll data sent by AJAX response
1052            *
1053            * @event core.viewtopic_modify_poll_ajax_data
1054            * @var    array    data                JSON response data
1055            * @var    array    valid_user_votes    Valid user votes
1056            * @var    array    vote_counts            Vote counts
1057            * @var    int        forum_id            Forum ID
1058            * @var    array    topic_data            Topic data
1059            * @var    array    poll_info            Array with the poll information
1060            * @since 3.2.4-RC1
1061            */
1062            $vars = array(
1063                'data',
1064                'valid_user_votes',
1065                'vote_counts',
1066                'forum_id',
1067                'topic_data',
1068                'poll_info',
1069            );
1070            extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_poll_ajax_data', compact($vars)));
1071
1072            $json_response = new \phpbb\json_response();
1073            $json_response->send($data);
1074        }
1075
1076        meta_refresh(5, $redirect_url);
1077        trigger_error($message);
1078    }
1079
1080    $poll_total = 0;
1081    $poll_most = 0;
1082    foreach ($poll_info as $poll_option)
1083    {
1084        $poll_total += $poll_option['poll_option_total'];
1085        $poll_most = ($poll_option['poll_option_total'] >= $poll_most) ? $poll_option['poll_option_total'] : $poll_most;
1086    }
1087
1088    $parse_flags = ($poll_info[0]['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES;
1089
1090    for ($i = 0, $size = count($poll_info); $i < $size; $i++)
1091    {
1092        $poll_info[$i]['poll_option_text'] = generate_text_for_display($poll_info[$i]['poll_option_text'], $poll_info[$i]['bbcode_uid'], $poll_option['bbcode_bitfield'], $parse_flags, true);
1093    }
1094
1095    $topic_data['poll_title'] = generate_text_for_display($topic_data['poll_title'], $poll_info[0]['bbcode_uid'], $poll_info[0]['bbcode_bitfield'], $parse_flags, true);
1096
1097    $poll_template_data = $poll_options_template_data = array();
1098    foreach ($poll_info as $poll_option)
1099    {
1100        $option_pct = ($poll_total > 0) ? $poll_option['poll_option_total'] / $poll_total : 0;
1101        $option_pct_txt = sprintf("%.1d%%", round($option_pct * 100));
1102        $option_pct_rel = ($poll_most > 0) ? $poll_option['poll_option_total'] / $poll_most : 0;
1103        $option_pct_rel_txt = sprintf("%.1d%%", round($option_pct_rel * 100));
1104        $option_most_votes = ($poll_option['poll_option_total'] > 0 && $poll_option['poll_option_total'] == $poll_most) ? true : false;
1105
1106        $poll_options_template_data[] = array(
1107            'POLL_OPTION_ID'             => $poll_option['poll_option_id'],
1108            'POLL_OPTION_CAPTION'         => $poll_option['poll_option_text'],
1109            'POLL_OPTION_RESULT'         => $poll_option['poll_option_total'],
1110            'POLL_OPTION_PERCENT'         => $option_pct_txt,
1111            'POLL_OPTION_PERCENT_REL'     => $option_pct_rel_txt,
1112            'POLL_OPTION_PCT'            => round($option_pct * 100),
1113            'POLL_OPTION_WIDTH'         => round($option_pct * 250),
1114            'POLL_OPTION_VOTED'            => (in_array($poll_option['poll_option_id'], $cur_voted_id)) ? true : false,
1115            'POLL_OPTION_MOST_VOTES'    => $option_most_votes,
1116        );
1117    }
1118
1119    $poll_end = $topic_data['poll_length'] + $topic_data['poll_start'];
1120
1121    $poll_template_data = array(
1122        'POLL_QUESTION'        => $topic_data['poll_title'],
1123        'TOTAL_VOTES'         => $poll_total,
1124        'POLL_LEFT_CAP_IMG'    => $user->img('poll_left'),
1125        'POLL_RIGHT_CAP_IMG'=> $user->img('poll_right'),
1126
1127        'L_MAX_VOTES'        => $user->lang('MAX_OPTIONS_SELECT', (int) $topic_data['poll_max_options']),
1128        'L_POLL_LENGTH'        => ($topic_data['poll_length']) ? sprintf($user->lang[($poll_end > time()) ? 'POLL_RUN_TILL' : 'POLL_ENDED_AT'], $user->format_date($poll_end)) : '',
1129
1130        'S_HAS_POLL'        => true,
1131        'S_CAN_VOTE'        => $s_can_vote,
1132        'S_DISPLAY_RESULTS'    => $s_display_results,
1133        'S_IS_MULTI_CHOICE'    => ($topic_data['poll_max_options'] > 1) ? true : false,
1134        'S_POLL_ACTION'        => $viewtopic_url,
1135
1136        'U_VIEW_RESULTS'    => $viewtopic_url . '&amp;view=viewpoll',
1137    );
1138
1139    /**
1140    * Event to add/modify poll template data
1141    *
1142    * @event core.viewtopic_modify_poll_template_data
1143    * @var    array    cur_voted_id                    Array with options' IDs current user has voted for
1144    * @var    int        poll_end                        The poll end time
1145    * @var    array    poll_info                        Array with the poll information
1146    * @var    array    poll_options_template_data        Array with the poll options template data
1147    * @var    array    poll_template_data                Array with the common poll template data
1148    * @var    int        poll_total                        Total poll votes count
1149    * @var    int        poll_most                        Mostly voted option votes count
1150    * @var    array    topic_data                        All the information from the topic and forum tables for this topic
1151    * @var    string    viewtopic_url                    URL to the topic page
1152    * @var    array    vote_counts                        Array with the vote counts for every poll option
1153    * @var    array    voted_id                        Array with updated options' IDs current user is voting for
1154    * @since 3.1.5-RC1
1155    */
1156    $vars = array(
1157        'cur_voted_id',
1158        'poll_end',
1159        'poll_info',
1160        'poll_options_template_data',
1161        'poll_template_data',
1162        'poll_total',
1163        'poll_most',
1164        'topic_data',
1165        'viewtopic_url',
1166        'vote_counts',
1167        'voted_id',
1168    );
1169    extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_poll_template_data', compact($vars)));
1170
1171    $template->assign_block_vars_array('poll_option', $poll_options_template_data);
1172
1173    $template->assign_vars($poll_template_data);
1174
1175    unset($poll_end, $poll_info, $poll_options_template_data, $poll_template_data, $voted_id);
1176}
1177
1178/** @var \phpbb\avatar\helper $avatar_helper */
1179$avatar_helper = $phpbb_container->get('avatar.helper');
1180
1181// If the user is trying to reach the second half of the topic, fetch it starting from the end
1182$store_reverse = false;
1183$sql_limit = $config['posts_per_page'];
1184$sql_sort_order = $direction = '';
1185
1186if ($start > $total_posts / 2)
1187{
1188    $store_reverse = true;
1189
1190    // Select the sort order
1191    $direction = (($sort_dir == 'd') ? 'ASC' : 'DESC');
1192
1193    $sql_limit = $pagination->reverse_limit($start, $sql_limit, $total_posts);
1194    $sql_start = $pagination->reverse_start($start, $sql_limit, $total_posts);
1195}
1196else
1197{
1198    // Select the sort order
1199    $direction = (($sort_dir == 'd') ? 'DESC' : 'ASC');
1200    $sql_start = $start;
1201}
1202
1203if (is_array($sort_by_sql[$sort_key]))
1204{
1205    $sql_sort_order = implode(' ' . $direction . ', ', $sort_by_sql[$sort_key]) . ' ' . $direction;
1206}
1207else
1208{
1209    $sql_sort_order = $sort_by_sql[$sort_key] . ' ' . $direction;
1210}
1211
1212// Container for user details, only process once
1213$post_list = $user_cache = $id_cache = $attachments = $attach_list = $rowset = $update_count = $post_edit_list = $post_delete_list = array();
1214$has_unapproved_attachments = $has_approved_attachments = $display_notice = false;
1215$i = $i_total = 0;
1216
1217// Go ahead and pull all data for this topic
1218$sql = 'SELECT p.post_id
1219    FROM ' . POSTS_TABLE . ' p' . (($join_user_sql[$sort_key]) ? ', ' . USERS_TABLE . ' u': '') . "
1220    WHERE p.topic_id = $topic_id
1221        AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id, 'p.') . "
1222        " . (($join_user_sql[$sort_key]) ? 'AND u.user_id = p.poster_id': '') . "
1223        $limit_posts_time
1224    ORDER BY $sql_sort_order";
1225
1226/**
1227* Event to modify the SQL query that gets post_list
1228*
1229* @event core.viewtopic_modify_post_list_sql
1230* @var    string    sql            The SQL query to generate the post_list
1231* @var    int        sql_limit    The number of posts the query fetches
1232* @var    int        sql_start    The index the query starts to fetch from
1233* @var    string    sort_key    Key the posts are sorted by
1234* @var    string    sort_days    Display posts of previous x days
1235* @var    int        forum_id    Forum ID
1236* @since 3.2.4-RC1
1237*/
1238$vars = array(
1239    'sql',
1240    'sql_limit',
1241    'sql_start',
1242    'sort_key',
1243    'sort_days',
1244    'forum_id',
1245);
1246extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_list_sql', compact($vars)));
1247
1248$result = $db->sql_query_limit($sql, $sql_limit, $sql_start);
1249
1250$i = ($store_reverse) ? $sql_limit - 1 : 0;
1251while ($row = $db->sql_fetchrow($result))
1252{
1253    $post_list[$i] = (int) $row['post_id'];
1254    ($store_reverse) ? $i-- : $i++;
1255}
1256$db->sql_freeresult($result);
1257
1258if (!count($post_list))
1259{
1260    if ($sort_days)
1261    {
1262        trigger_error('NO_POSTS_TIME_FRAME');
1263    }
1264    else
1265    {
1266        trigger_error('NO_TOPIC');
1267    }
1268}
1269
1270// Holding maximum post time for marking topic read
1271// We need to grab it because we do reverse ordering sometimes
1272$max_post_time = 0;
1273
1274$sql_ary = array(
1275    'SELECT'    => 'u.*, z.friend, z.foe, p.*',
1276
1277    'FROM'        => array(
1278        USERS_TABLE        => 'u',
1279        POSTS_TABLE        => 'p',
1280    ),
1281
1282    'LEFT_JOIN'    => array(
1283        array(
1284            'FROM'    => array(ZEBRA_TABLE => 'z'),
1285            'ON'    => 'z.user_id = ' . $user->data['user_id'] . ' AND z.zebra_id = p.poster_id',
1286        ),
1287    ),
1288
1289    'WHERE'        => $db->sql_in_set('p.post_id', $post_list) . '
1290        AND u.user_id = p.poster_id',
1291);
1292
1293/**
1294* Event to modify the SQL query before the post and poster data is retrieved
1295*
1296* @event core.viewtopic_get_post_data
1297* @var    int        forum_id    Forum ID
1298* @var    int        topic_id    Topic ID
1299* @var    array    topic_data    Array with topic data
1300* @var    array    post_list    Array with post_ids we are going to retrieve
1301* @var    int        sort_days    Display posts of previous x days
1302* @var    string    sort_key    Key the posts are sorted by
1303* @var    string    sort_dir    Direction the posts are sorted by
1304* @var    int        start        Pagination information
1305* @var    array    sql_ary        The SQL array to get the data of posts and posters
1306* @since 3.1.0-a1
1307* @changed 3.1.0-a2 Added vars forum_id, topic_id, topic_data, post_list, sort_days, sort_key, sort_dir, start
1308*/
1309$vars = array(
1310    'forum_id',
1311    'topic_id',
1312    'topic_data',
1313    'post_list',
1314    'sort_days',
1315    'sort_key',
1316    'sort_dir',
1317    'start',
1318    'sql_ary',
1319);
1320extract($phpbb_dispatcher->trigger_event('core.viewtopic_get_post_data', compact($vars)));
1321
1322$sql = $db->sql_build_query('SELECT', $sql_ary);
1323$result = $db->sql_query($sql);
1324
1325$now = $user->create_datetime();
1326$now = phpbb_gmgetdate($now->getTimestamp() + $now->getOffset());
1327
1328// Posts are stored in the $rowset array while $attach_list, $user_cache
1329// and the global bbcode_bitfield are built
1330while ($row = $db->sql_fetchrow($result))
1331{
1332    // Set max_post_time
1333    if ($row['post_time'] > $max_post_time)
1334    {
1335        $max_post_time = $row['post_time'];
1336    }
1337
1338    $poster_id = (int) $row['poster_id'];
1339
1340    // Does post have an attachment? If so, add it to the list
1341    if ($row['post_attachment'] && $config['allow_attachments'])
1342    {
1343        $attach_list[] = (int) $row['post_id'];
1344
1345        if ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE)
1346        {
1347            $has_unapproved_attachments = true;
1348        }
1349        else if ($row['post_visibility'] == ITEM_APPROVED)
1350        {
1351            $has_approved_attachments = true;
1352        }
1353    }
1354
1355    $rowset_data = array(
1356        'hide_post'            => (($row['foe'] || $row['post_visibility'] == ITEM_DELETED) && ($view != 'show' || $post_id != $row['post_id'])) ? true : false,
1357
1358        'post_id'            => $row['post_id'],
1359        'post_time'            => $row['post_time'],
1360        'user_id'            => $row['user_id'],
1361        'username'            => $row['username'],
1362        'user_colour'        => $row['user_colour'],
1363        'topic_id'            => $row['topic_id'],
1364        'forum_id'            => $row['forum_id'],
1365        'post_subject'        => $row['post_subject'],
1366        'post_edit_count'    => $row['post_edit_count'],
1367        'post_edit_time'    => $row['post_edit_time'],
1368        'post_edit_reason'    => $row['post_edit_reason'],
1369        'post_edit_user'    => $row['post_edit_user'],
1370        'post_edit_locked'    => $row['post_edit_locked'],
1371        'post_delete_time'    => $row['post_delete_time'],
1372        'post_delete_reason'=> $row['post_delete_reason'],
1373        'post_delete_user'    => $row['post_delete_user'],
1374
1375        // Make sure the icon actually exists
1376        'icon_id'            => (isset($icons[$row['icon_id']]['img'], $icons[$row['icon_id']]['height'], $icons[$row['icon_id']]['width'])) ? $row['icon_id'] : 0,
1377        'post_attachment'    => $row['post_attachment'],
1378        'post_visibility'    => $row['post_visibility'],
1379        'post_reported'        => $row['post_reported'],
1380        'post_username'        => $row['post_username'],
1381        'post_text'            => $row['post_text'],
1382        'bbcode_uid'        => $row['bbcode_uid'],
1383        'bbcode_bitfield'    => $row['bbcode_bitfield'],
1384        'enable_smilies'    => $row['enable_smilies'],
1385        'enable_sig'        => $row['enable_sig'],
1386        'friend'            => $row['friend'],
1387        'foe'                => $row['foe'],
1388    );
1389
1390    /**
1391    * Modify the post rowset containing data to be displayed with posts
1392    *
1393    * @event core.viewtopic_post_rowset_data
1394    * @var    array    rowset_data    Array with the rowset data for this post
1395    * @var    array    row            Array with original user and post data
1396    * @since 3.1.0-a1
1397    */
1398    $vars = array('rowset_data', 'row');
1399    extract($phpbb_dispatcher->trigger_event('core.viewtopic_post_rowset_data', compact($vars)));
1400
1401    $rowset[$row['post_id']] = $rowset_data;
1402
1403    // Cache various user specific data ... so we don't have to recompute
1404    // this each time the same user appears on this page
1405    if (!isset($user_cache[$poster_id]))
1406    {
1407        if ($poster_id == ANONYMOUS)
1408        {
1409            $user_cache_data = array(
1410                'user_type'        => USER_IGNORE,
1411                'joined'        => '',
1412                'posts'            => '',
1413
1414                'sig'                    => '',
1415                'sig_bbcode_uid'        => '',
1416                'sig_bbcode_bitfield'    => '',
1417
1418                'online'            => false,
1419                'avatar'            => ($user->optionget('viewavatars')) ? $avatar_helper->get_user_avatar($row) : [],
1420                'rank_title'        => '',
1421                'rank_image'        => '',
1422                'rank_image_src'    => '',
1423                'pm'                => '',
1424                'email'                => '',
1425                'search'            => '',
1426                'age'                => '',
1427
1428                'username'            => $row['username'],
1429                'user_colour'        => $row['user_colour'],
1430                'contact_user'        => '',
1431
1432                'warnings'            => 0,
1433                'allow_pm'            => 0,
1434            );
1435
1436            /**
1437            * Modify the guest user's data displayed with the posts
1438            *
1439            * @event core.viewtopic_cache_guest_data
1440            * @var    array    user_cache_data    Array with the user's data
1441            * @var    int        poster_id        Poster's user id
1442            * @var    array    row                Array with original user and post data
1443            * @since 3.1.0-a1
1444            */
1445            $vars = array('user_cache_data', 'poster_id', 'row');
1446            extract($phpbb_dispatcher->trigger_event('core.viewtopic_cache_guest_data', compact($vars)));
1447
1448            $user_cache[$poster_id] = $user_cache_data;
1449
1450            $user_rank_data = phpbb_get_user_rank($row, false);
1451            $user_cache[$poster_id]['rank_title'] = $user_rank_data['title'];
1452            $user_cache[$poster_id]['rank_image'] = $user_rank_data['img'];
1453            $user_cache[$poster_id]['rank_image_src'] = $user_rank_data['img_src'];
1454        }
1455        else
1456        {
1457            $user_sig = '';
1458
1459            // We add the signature to every posters entry because enable_sig is post dependent
1460            if ($row['user_sig'] && $config['allow_sig'] && $user->optionget('viewsigs'))
1461            {
1462                $user_sig = $row['user_sig'];
1463            }
1464
1465            $id_cache[] = $poster_id;
1466
1467            $user_cache_data = array(
1468                'user_type'                    => $row['user_type'],
1469                'user_inactive_reason'        => $row['user_inactive_reason'],
1470
1471                'joined'        => $user->format_date($row['user_regdate']),
1472                'posts'            => $row['user_posts'],
1473                'warnings'        => (isset($row['user_warnings'])) ? $row['user_warnings'] : 0,
1474
1475                'sig'                    => $user_sig,
1476                'sig_bbcode_uid'        => (!empty($row['user_sig_bbcode_uid'])) ? $row['user_sig_bbcode_uid'] : '',
1477                'sig_bbcode_bitfield'    => (!empty($row['user_sig_bbcode_bitfield'])) ? $row['user_sig_bbcode_bitfield'] : '',
1478
1479                'viewonline'    => $row['user_allow_viewonline'],
1480                'allow_pm'        => $row['user_allow_pm'],
1481
1482                'avatar'        => ($user->optionget('viewavatars')) ? $avatar_helper->get_user_avatar($row) : [],
1483                'age'            => '',
1484
1485                'rank_title'        => '',
1486                'rank_image'        => '',
1487                'rank_image_src'    => '',
1488
1489                'username'            => $row['username'],
1490                'user_colour'        => $row['user_colour'],
1491                'contact_user'         => $user->lang('CONTACT_USER', get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['username'])),
1492
1493                'online'        => false,
1494                'search'        => ($config['load_search'] && $auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id=$poster_id&amp;sr=posts") : '',
1495
1496                'author_full'        => get_username_string('full', $poster_id, $row['username'], $row['user_colour']),
1497                'author_colour'        => get_username_string('colour', $poster_id, $row['username'], $row['user_colour']),
1498                'author_username'    => get_username_string('username', $poster_id, $row['username'], $row['user_colour']),
1499                'author_profile'    => get_username_string('profile', $poster_id, $row['username'], $row['user_colour']),
1500            );
1501
1502            /**
1503            * Modify the users' data displayed with their posts
1504            *
1505            * @event core.viewtopic_cache_user_data
1506            * @var    array    user_cache_data    Array with the user's data
1507            * @var    int        poster_id        Poster's user id
1508            * @var    array    row                Array with original user and post data
1509            * @since 3.1.0-a1
1510            */
1511            $vars = array('user_cache_data', 'poster_id', 'row');
1512            extract($phpbb_dispatcher->trigger_event('core.viewtopic_cache_user_data', compact($vars)));
1513
1514            $user_cache[$poster_id] = $user_cache_data;
1515
1516            $user_rank_data = phpbb_get_user_rank($row, $row['user_posts']);
1517            $user_cache[$poster_id]['rank_title'] = $user_rank_data['title'];
1518            $user_cache[$poster_id]['rank_image'] = $user_rank_data['img'];
1519            $user_cache[$poster_id]['rank_image_src'] = $user_rank_data['img_src'];
1520
1521            if ((!empty($row['user_allow_viewemail']) && $auth->acl_get('u_sendemail')) || $auth->acl_get('a_email'))
1522            {
1523                $user_cache[$poster_id]['email'] = ($config['board_email_form'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=email&amp;u=$poster_id") : (($config['board_hide_emails'] && !$auth->acl_get('a_email')) ? '' : 'mailto:' . $row['user_email']);
1524            }
1525            else
1526            {
1527                $user_cache[$poster_id]['email'] = '';
1528            }
1529
1530            if ($config['allow_birthdays'] && !empty($row['user_birthday']))
1531            {
1532                list($bday_day, $bday_month, $bday_year) = array_map('intval', explode('-', $row['user_birthday']));
1533
1534                if ($bday_year)
1535                {
1536                    $diff = $now['mon'] - $bday_month;
1537                    if ($diff == 0)
1538                    {
1539                        $diff = ($now['mday'] - $bday_day < 0) ? 1 : 0;
1540                    }
1541                    else
1542                    {
1543                        $diff = ($diff < 0) ? 1 : 0;
1544                    }
1545
1546                    $user_cache[$poster_id]['age'] = (int) ($now['year'] - $bday_year - $diff);
1547                }
1548            }
1549        }
1550    }
1551}
1552$db->sql_freeresult($result);
1553
1554// Load custom profile fields
1555if ($config['load_cpf_viewtopic'])
1556{
1557    /* @var $cp \phpbb\profilefields\manager */
1558    $cp = $phpbb_container->get('profilefields.manager');
1559
1560    // Grab all profile fields from users in id cache for later use - similar to the poster cache
1561    $profile_fields_tmp = $cp->grab_profile_fields_data($id_cache);
1562
1563    // filter out fields not to be displayed on viewtopic. Yes, it's a hack, but this shouldn't break any MODs.
1564    $profile_fields_cache = array();
1565    foreach ($profile_fields_tmp as $profile_user_id => $profile_fields)
1566    {
1567        $profile_fields_cache[$profile_user_id] = array();
1568        foreach ($profile_fields as $used_ident => $profile_field)
1569        {
1570            if ($profile_field['data']['field_show_on_vt'])
1571            {
1572                $profile_fields_cache[$profile_user_id][$used_ident] = $profile_field;
1573            }
1574        }
1575    }
1576    unset($profile_fields_tmp);
1577}
1578
1579// Generate online information for user
1580if ($config['load_onlinetrack'] && count($id_cache))
1581{
1582    $sql = 'SELECT session_user_id, MAX(session_time) as online_time, MIN(session_viewonline) AS viewonline
1583        FROM ' . SESSIONS_TABLE . '
1584        WHERE ' . $db->sql_in_set('session_user_id', $id_cache) . '
1585        GROUP BY session_user_id';
1586    $result = $db->sql_query($sql);
1587
1588    $update_time = $config['load_online_time'] * 60;
1589    while ($row = $db->sql_fetchrow($result))
1590    {
1591        $user_cache[$row['session_user_id']]['online'] = (time() - $update_time < $row['online_time'] && (($row['viewonline']) || $auth->acl_get('u_viewonline'))) ? true : false;
1592    }
1593    $db->sql_freeresult($result);
1594}
1595unset($id_cache);
1596
1597// Pull attachment data
1598if (count($attach_list))
1599{
1600    if ($auth->acl_get('u_download') && $auth->acl_get('f_download', $forum_id))
1601    {
1602        $sql = 'SELECT *
1603            FROM ' . ATTACHMENTS_TABLE . '
1604            WHERE ' . $db->sql_in_set('post_msg_id', $attach_list) . '
1605                AND in_message = 0
1606            ORDER BY attach_id DESC, post_msg_id ASC';
1607        $result = $db->sql_query($sql);
1608
1609        while ($row = $db->sql_fetchrow($result))
1610        {
1611            $attachments[$row['post_msg_id']][] = $row;
1612        }
1613        $db->sql_freeresult($result);
1614
1615        // No attachments exist, but post table thinks they do so go ahead and reset post_attach flags
1616        if (!count($attachments))
1617        {
1618            $sql = 'UPDATE ' . POSTS_TABLE . '
1619                SET post_attachment = 0
1620                WHERE ' . $db->sql_in_set('post_id', $attach_list);
1621            $db->sql_query($sql);
1622
1623            // We need to update the topic indicator too if the complete topic is now without an attachment
1624            if (count($rowset) != $total_posts)
1625            {
1626                // Not all posts are displayed so we query the db to find if there's any attachment for this topic
1627                $sql = 'SELECT a.post_msg_id as post_id
1628                    FROM ' . ATTACHMENTS_TABLE . ' a, ' . POSTS_TABLE . " p
1629                    WHERE p.topic_id = $topic_id
1630                        AND p.post_visibility = " . ITEM_APPROVED . '
1631                        AND p.topic_id = a.topic_id';
1632                $result = $db->sql_query_limit($sql, 1);
1633                $row = $db->sql_fetchrow($result);
1634                $db->sql_freeresult($result);
1635
1636                if (!$row)
1637                {
1638                    $sql = 'UPDATE ' . TOPICS_TABLE . "
1639                        SET topic_attachment = 0
1640                        WHERE topic_id = $topic_id";
1641                    $db->sql_query($sql);
1642                }
1643            }
1644            else
1645            {
1646                $sql = 'UPDATE ' . TOPICS_TABLE . "
1647                    SET topic_attachment = 0
1648                    WHERE topic_id = $topic_id";
1649                $db->sql_query($sql);
1650            }
1651        }
1652        else if ($has_approved_attachments && !$topic_data['topic_attachment'])
1653        {
1654            // Topic has approved attachments but its flag is wrong
1655            $sql = 'UPDATE ' . TOPICS_TABLE . "
1656                SET topic_attachment = 1
1657                WHERE topic_id = $topic_id";
1658            $db->sql_query($sql);
1659
1660            $topic_data['topic_attachment'] = 1;
1661        }
1662        else if ($has_unapproved_attachments && !$topic_data['topic_attachment'])
1663        {
1664            // Topic has only unapproved attachments but we have the right to see and download them
1665            $topic_data['topic_attachment'] = 1;
1666        }
1667    }
1668    else
1669    {
1670        $display_notice = true;
1671    }
1672}
1673
1674if ($config['enable_accurate_pm_button'])
1675{
1676    // Get the list of users who can receive private messages
1677    $can_receive_pm_list = $auth->acl_get_list(array_keys($user_cache), 'u_readpm');
1678    $can_receive_pm_list = (empty($can_receive_pm_list) || !isset($can_receive_pm_list[0]['u_readpm'])) ? array() : $can_receive_pm_list[0]['u_readpm'];
1679
1680    // Get the list of permanently banned users
1681    $permanently_banned_users = phpbb_get_banned_user_ids(array_keys($user_cache), false);
1682}
1683else
1684{
1685    $can_receive_pm_list = array_keys($user_cache);
1686    $permanently_banned_users = [];
1687}
1688
1689$i_total = count($rowset) - 1;
1690$prev_post_id = '';
1691
1692$template->assign_vars(array(
1693    'S_HAS_ATTACHMENTS' => $topic_data['topic_attachment'],
1694    'S_NUM_POSTS' => count($post_list))
1695);
1696
1697/**
1698* Event to modify the post, poster and attachment data before assigning the posts
1699*
1700* @event core.viewtopic_modify_post_data
1701* @var    int        forum_id    Forum ID
1702* @var    int        topic_id    Topic ID
1703* @var    array    topic_data    Array with topic data
1704* @var    array    post_list    Array with post_ids we are going to display
1705* @var    array    rowset        Array with post_id => post data
1706* @var    array    user_cache    Array with prepared user data
1707* @var    int        start        Pagination information
1708* @var    int        sort_days    Display posts of previous x days
1709* @var    string    sort_key    Key the posts are sorted by
1710* @var    string    sort_dir    Direction the posts are sorted by
1711* @var    bool    display_notice                Shall we display a notice instead of attachments
1712* @var    bool    has_approved_attachments    Does the topic have approved attachments
1713* @var    array    attachments                    List of attachments post_id => array of attachments
1714* @var    array    permanently_banned_users    List of permanently banned users
1715* @var    array    can_receive_pm_list            Array with posters that can receive pms
1716* @since 3.1.0-RC3
1717*/
1718$vars = array(
1719    'forum_id',
1720    'topic_id',
1721    'topic_data',
1722    'post_list',
1723    'rowset',
1724    'user_cache',
1725    'sort_days',
1726    'sort_key',
1727    'sort_dir',
1728    'start',
1729    'permanently_banned_users',
1730    'can_receive_pm_list',
1731    'display_notice',
1732    'has_approved_attachments',
1733    'attachments',
1734);
1735extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_data', compact($vars)));
1736
1737// Output the posts
1738$first_unread = $post_unread = false;
1739for ($i = 0, $end = count($post_list); $i < $end; ++$i)
1740{
1741    // A non-existing rowset only happens if there was no user present for the entered poster_id
1742    // This could be a broken posts table.
1743    if (!isset($rowset[$post_list[$i]]))
1744    {
1745        continue;
1746    }
1747
1748    $row = $rowset[$post_list[$i]];
1749    $poster_id = $row['user_id'];
1750
1751    // End signature parsing, only if needed
1752    if ($user_cache[$poster_id]['sig'] && $row['enable_sig'] && empty($user_cache[$poster_id]['sig_parsed']))
1753    {
1754        $parse_flags = ($user_cache[$poster_id]['sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES;
1755        $user_cache[$poster_id]['sig'] = generate_text_for_display($user_cache[$poster_id]['sig'], $user_cache[$poster_id]['sig_bbcode_uid'], $user_cache[$poster_id]['sig_bbcode_bitfield'],  $parse_flags, true);
1756        $user_cache[$poster_id]['sig_parsed'] = true;
1757    }
1758
1759    // Parse the message and subject
1760    $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES;
1761    $message = generate_text_for_display($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, true);
1762
1763    if (!empty($attachments[$row['post_id']]))
1764    {
1765        parse_attachments($forum_id, $message, $attachments[$row['post_id']], $update_count);
1766    }
1767
1768    // Replace naughty words such as farty pants
1769    $row['post_subject'] = censor_text($row['post_subject']);
1770
1771    // Highlight active words (primarily for search)
1772    if ($highlight_match)
1773    {
1774        $message = preg_replace('#(?!<.*)(?<!\w)(' . $highlight_match . ')(?!\w|[^<>]*(?:</s(?:cript|tyle))?>)#is', '<span class="posthilit">\1</span>', $message);
1775        $row['post_subject'] = preg_replace('#(?!<.*)(?<!\w)(' . $highlight_match . ')(?!\w|[^<>]*(?:</s(?:cript|tyle))?>)#is', '<span class="posthilit">\1</span>', $row['post_subject']);
1776    }
1777
1778    // Editing information
1779    if (($row['post_edit_count'] && $config['display_last_edited']) || $row['post_edit_reason'])
1780    {
1781        // Get usernames for all following posts if not already stored
1782        if (!count($post_edit_list) && ($row['post_edit_reason'] || ($row['post_edit_user'] && !isset($user_cache[$row['post_edit_user']]))))
1783        {
1784            // Remove all post_ids already parsed (we do not have to check them)
1785            $post_storage_list = (!$store_reverse) ? array_slice($post_list, $i) : array_slice(array_reverse($post_list), $i);
1786
1787            $sql = 'SELECT DISTINCT u.user_id, u.username, u.user_colour
1788                FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u
1789                WHERE ' . $db->sql_in_set('p.post_id', $post_storage_list) . '
1790                    AND p.post_edit_count <> 0
1791                    AND p.post_edit_user <> 0
1792                    AND p.post_edit_user = u.user_id';
1793            $result2 = $db->sql_query($sql);
1794            while ($user_edit_row = $db->sql_fetchrow($result2))
1795            {
1796                $post_edit_list[$user_edit_row['user_id']] = $user_edit_row;
1797            }
1798            $db->sql_freeresult($result2);
1799
1800            unset($post_storage_list);
1801        }
1802
1803        if ($row['post_edit_reason'])
1804        {
1805            // User having edited the post also being the post author?
1806            if (!$row['post_edit_user'] || $row['post_edit_user'] == $poster_id)
1807            {
1808                $display_username = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']);
1809            }
1810            else
1811            {
1812                $display_username = get_username_string('full', $row['post_edit_user'], $post_edit_list[$row['post_edit_user']]['username'], $post_edit_list[$row['post_edit_user']]['user_colour']);
1813            }
1814
1815            $l_edited_by = $user->lang('EDITED_TIMES_TOTAL', (int) $row['post_edit_count'], $display_username, $user->format_date($row['post_edit_time'], false, true));
1816        }
1817        else
1818        {
1819            if ($row['post_edit_user'] && !isset($user_cache[$row['post_edit_user']]))
1820            {
1821                $user_cache[$row['post_edit_user']] = $post_edit_list[$row['post_edit_user']];
1822            }
1823
1824            // User having edited the post also being the post author?
1825            if (!$row['post_edit_user'] || $row['post_edit_user'] == $poster_id)
1826            {
1827                $display_username = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']);
1828            }
1829            else
1830            {
1831                $display_username = get_username_string('full', $row['post_edit_user'], $user_cache[$row['post_edit_user']]['username'], $user_cache[$row['post_edit_user']]['user_colour']);
1832            }
1833
1834            $l_edited_by = $user->lang('EDITED_TIMES_TOTAL', (int) $row['post_edit_count'], $display_username, $user->format_date($row['post_edit_time'], false, true));
1835        }
1836    }
1837    else
1838    {
1839        $l_edited_by = '';
1840    }
1841
1842    // Deleting information
1843    if ($row['post_visibility'] == ITEM_DELETED && $row['post_delete_user'])
1844    {
1845        // Get usernames for all following posts if not already stored
1846        if (!count($post_delete_list) && ($row['post_delete_reason'] || ($row['post_delete_user'] && !isset($user_cache[$row['post_delete_user']]))))
1847        {
1848            // Remove all post_ids already parsed (we do not have to check them)
1849            $post_storage_list = (!$store_reverse) ? array_slice($post_list, $i) : array_slice(array_reverse($post_list), $i);
1850
1851            $sql = 'SELECT DISTINCT u.user_id, u.username, u.user_colour
1852                FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u
1853                WHERE ' . $db->sql_in_set('p.post_id', $post_storage_list) . '
1854                    AND p.post_delete_user <> 0
1855                    AND p.post_delete_user = u.user_id';
1856            $result2 = $db->sql_query($sql);
1857            while ($user_delete_row = $db->sql_fetchrow($result2))
1858            {
1859                $post_delete_list[$user_delete_row['user_id']] = $user_delete_row;
1860            }
1861            $db->sql_freeresult($result2);
1862
1863            unset($post_storage_list);
1864        }
1865
1866        if ($row['post_delete_user'] && !isset($user_cache[$row['post_delete_user']]))
1867        {
1868            $user_cache[$row['post_delete_user']] = $post_delete_list[$row['post_delete_user']];
1869        }
1870
1871        $display_postername = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']);
1872
1873        // User having deleted the post also being the post author?
1874        if (!$row['post_delete_user'] || $row['post_delete_user'] == $poster_id)
1875        {
1876            $display_username = $display_postername;
1877        }
1878        else
1879        {
1880            $display_username = get_username_string('full', $row['post_delete_user'], $user_cache[$row['post_delete_user']]['username'], $user_cache[$row['post_delete_user']]['user_colour']);
1881        }
1882
1883        if ($row['post_delete_reason'])
1884        {
1885            $l_deleted_message = $user->lang('POST_DELETED_BY_REASON', $display_postername, $display_username, $user->format_date($row['post_delete_time'], false, true), $row['post_delete_reason']);
1886        }
1887        else
1888        {
1889            $l_deleted_message = $user->lang('POST_DELETED_BY', $display_postername, $display_username, $user->format_date($row['post_delete_time'], false, true));
1890        }
1891        $l_deleted_by = $user->lang('DELETED_INFORMATION', $display_username, $user->format_date($row['post_delete_time'], false, true));
1892    }
1893    else
1894    {
1895        $l_deleted_by = $l_deleted_message = '';
1896    }
1897
1898    // Bump information
1899    if ($topic_data['topic_bumped'] && $row['post_id'] == $topic_data['topic_last_post_id'] && isset($user_cache[$topic_data['topic_bumper']]) )
1900    {
1901        // It is safe to grab the username from the user cache array, we are at the last
1902        // post and only the topic poster and last poster are allowed to bump.
1903        // Admins and mods are bound to the above rules too...
1904        $l_bumped_by = sprintf($user->lang['BUMPED_BY'], $user_cache[$topic_data['topic_bumper']]['username'], $user->format_date($topic_data['topic_last_post_time'], false, true));
1905    }
1906    else
1907    {
1908        $l_bumped_by = '';
1909    }
1910
1911    $cp_row = array();
1912
1913    //
1914    if ($config['load_cpf_viewtopic'])
1915    {
1916        $cp_row = (isset($profile_fields_cache[$poster_id])) ? $cp->generate_profile_fields_template_data($profile_fields_cache[$poster_id]) : array();
1917    }
1918
1919    $post_unread = (isset($topic_tracking_info[$topic_id]) && $row['post_time'] > $topic_tracking_info[$topic_id]) ? true : false;
1920
1921    $s_first_unread = false;
1922    if (!$first_unread && $post_unread)
1923    {
1924        $s_first_unread = $first_unread = true;
1925    }
1926
1927    $force_edit_allowed = $force_delete_allowed = $force_softdelete_allowed = false;
1928
1929    $s_cannot_edit = !$auth->acl_get('f_edit', $forum_id) || $user->data['user_id'] != $poster_id;
1930    $s_cannot_edit_time = $config['edit_time'] && $row['post_time'] <= time() - ($config['edit_time'] * 60);
1931    $s_cannot_edit_locked = ($topic_data['topic_status'] == ITEM_LOCKED && !$auth->acl_get('m_lock', $forum_id)) || $row['post_edit_locked'];
1932
1933    $s_cannot_delete = $user->data['user_id'] != $poster_id || (
1934            !$auth->acl_get('f_delete', $forum_id) &&
1935            (!$auth->acl_get('f_softdelete', $forum_id) || $row['post_visibility'] == ITEM_DELETED)
1936    );
1937    $s_cannot_delete_lastpost = $topic_data['topic_last_post_id'] != $row['post_id'];
1938    $s_cannot_delete_time = $config['delete_time'] && $row['post_time'] <= time() - ($config['delete_time'] * 60);
1939    // we do not want to allow removal of the last post if a moderator locked it!
1940    $s_cannot_delete_locked = $topic_data['topic_status'] == ITEM_LOCKED || $row['post_edit_locked'];
1941
1942    /**
1943    * This event allows you to modify the conditions for the "can edit post" and "can delete post" checks
1944    *
1945    * @event core.viewtopic_modify_post_action_conditions
1946    * @var    array    row            Array with post data
1947    * @var    array    topic_data    Array with topic data
1948    * @var    bool    force_edit_allowed        Allow the user to edit the post (all permissions and conditions are ignored)
1949    * @var    bool    s_cannot_edit            User can not edit the post because it's not his
1950    * @var    bool    s_cannot_edit_locked    User can not edit the post because it's locked
1951    * @var    bool    s_cannot_edit_time        User can not edit the post because edit_time has passed
1952    * @var    bool    force_delete_allowed        Allow the user to delete the post (all permissions and conditions are ignored)
1953    * @var    bool    s_cannot_delete                User can not delete the post because it's not his
1954    * @var    bool    s_cannot_delete_lastpost    User can not delete the post because it's not the last post of the topic
1955    * @var    bool    s_cannot_delete_locked        User can not delete the post because it's locked
1956    * @var    bool    s_cannot_delete_time        User can not delete the post because edit_time has passed
1957    * @var    bool    force_softdelete_allowed    Allow the user to Ñ‹oftdelete the post (all permissions and conditions are ignored)
1958    * @since 3.1.0-b4
1959    * @changed 3.1.11-RC1 Added force_softdelete_allowed var
1960    */
1961    $vars = array(
1962        'row',
1963        'topic_data',
1964        'force_edit_allowed',
1965        's_cannot_edit',
1966        's_cannot_edit_locked',
1967        's_cannot_edit_time',
1968        'force_delete_allowed',
1969        's_cannot_delete',
1970        's_cannot_delete_lastpost',
1971        's_cannot_delete_locked',
1972        's_cannot_delete_time',
1973        'force_softdelete_allowed',
1974    );
1975    extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_action_conditions', compact($vars)));
1976
1977    $edit_allowed = $force_edit_allowed || ($user->data['is_registered'] && ($auth->acl_get('m_edit', $forum_id) || (
1978        !$s_cannot_edit &&
1979        !$s_cannot_edit_time &&
1980        !$s_cannot_edit_locked
1981    )));
1982
1983    $quote_allowed = $auth->acl_get('m_edit', $forum_id) || ($topic_data['topic_status'] != ITEM_LOCKED &&
1984        ($user->data['user_id'] == ANONYMOUS || $auth->acl_get('f_reply', $forum_id))
1985    );
1986
1987    // Only display the quote button if the post is quotable.  Posts not approved are not quotable.
1988    $quote_allowed = ($quote_allowed && $row['post_visibility'] == ITEM_APPROVED) ? true : false;
1989
1990    $delete_allowed = $force_delete_allowed || ($user->data['is_registered'] && (
1991        ($auth->acl_get('m_delete', $forum_id) || ($auth->acl_get('m_softdelete', $forum_id) && $row['post_visibility'] != ITEM_DELETED)) ||
1992        (!$s_cannot_delete && !$s_cannot_delete_lastpost && !$s_cannot_delete_time && !$s_cannot_delete_locked)
1993    ));
1994
1995    $softdelete_allowed = $force_softdelete_allowed || (($auth->acl_get('m_softdelete', $forum_id) ||
1996        ($auth->acl_get('f_softdelete', $forum_id) && $user->data['user_id'] == $poster_id)) && ($row['post_visibility'] != ITEM_DELETED));
1997
1998    $permanent_delete_allowed = $force_delete_allowed || ($auth->acl_get('m_delete', $forum_id) ||
1999        ($auth->acl_get('f_delete', $forum_id) && $user->data['user_id'] == $poster_id));
2000
2001    // Can this user receive a Private Message?
2002    $can_receive_pm = (
2003        // They must be a "normal" user
2004        $user_cache[$poster_id]['user_type'] != USER_IGNORE &&
2005
2006        // They must not be deactivated by the administrator
2007        ($user_cache[$poster_id]['user_type'] != USER_INACTIVE || $user_cache[$poster_id]['user_inactive_reason'] != INACTIVE_MANUAL) &&
2008
2009        // They must be able to read PMs
2010        in_array($poster_id, $can_receive_pm_list) &&
2011
2012        // They must not be permanently banned
2013        !in_array($poster_id, $permanently_banned_users) &&
2014
2015        // They must allow users to contact via PM
2016        (($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) || $user_cache[$poster_id]['allow_pm'])
2017    );
2018
2019    $u_pm = '';
2020
2021    if ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && $can_receive_pm)
2022    {
2023        $u_pm = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=compose&amp;action=quotepost&amp;p=' . $row['post_id']);
2024    }
2025
2026    $post_row = array(
2027        'POST_AUTHOR_FULL'        => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_full'] : get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']),
2028        'POST_AUTHOR_COLOUR'    => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_colour'] : get_username_string('colour', $poster_id, $row['username'], $row['user_colour'], $row['post_username']),
2029        'POST_AUTHOR'            => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_username'] : get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['post_username']),
2030        'U_POST_AUTHOR'            => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_profile'] : get_username_string('profile', $poster_id, $row['username'], $row['user_colour'], $row['post_username']),
2031
2032        'RANK_TITLE'        => $user_cache[$poster_id]['rank_title'],
2033        'RANK_IMG'            => $user_cache[$poster_id]['rank_image'],
2034        'RANK_IMG_SRC'        => $user_cache[$poster_id]['rank_image_src'],
2035        'POSTER_JOINED'        => $user_cache[$poster_id]['joined'],
2036        'POSTER_POSTS'        => $user_cache[$poster_id]['posts'],
2037        'POSTER_WARNINGS'    => $auth->acl_get('m_warn') ? $user_cache[$poster_id]['warnings'] : '',
2038        'POSTER_AGE'        => $user_cache[$poster_id]['age'],
2039        'CONTACT_USER'        => $user_cache[$poster_id]['contact_user'],
2040
2041        'POST_DATE'            => $user->format_date($row['post_time'], false, ($view == 'print') ? true : false),
2042        'POST_DATE_RFC3339'    => gmdate(DATE_RFC3339, $row['post_time']),
2043        'POST_SUBJECT'        => $row['post_subject'],
2044        'MESSAGE'            => $message,
2045        'SIGNATURE'            => ($row['enable_sig']) ? $user_cache[$poster_id]['sig'] : '',
2046        'EDITED_MESSAGE'    => $l_edited_by,
2047        'EDIT_REASON'        => $row['post_edit_reason'],
2048        'DELETED_MESSAGE'    => $l_deleted_by,
2049        'DELETE_REASON'        => $row['post_delete_reason'],
2050        'BUMPED_MESSAGE'    => $l_bumped_by,
2051
2052        'MINI_POST_IMG'            => ($post_unread) ? $user->img('icon_post_target_unread', 'UNREAD_POST') : $user->img('icon_post_target', 'POST'),
2053        'POST_ICON_IMG'            => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['img'] : '',
2054        'POST_ICON_IMG_WIDTH'    => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['width'] : '',
2055        'POST_ICON_IMG_HEIGHT'    => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['height'] : '',
2056        'POST_ICON_IMG_ALT'     => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['alt'] : '',
2057        'ONLINE_IMG'            => ($poster_id == ANONYMOUS || !$config['load_onlinetrack']) ? '' : (($user_cache[$poster_id]['online']) ? $user->img('icon_user_online', 'ONLINE') : $user->img('icon_user_offline', 'OFFLINE')),
2058        'S_ONLINE'                => ($poster_id == ANONYMOUS || !$config['load_onlinetrack']) ? false : (($user_cache[$poster_id]['online']) ? true : false),
2059
2060        'U_EDIT'            => ($edit_allowed) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=edit&amp;p={$row['post_id']}") : '',
2061        'U_QUOTE'            => ($quote_allowed) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=quote&amp;p={$row['post_id']}") : '',
2062        'U_INFO'            => ($auth->acl_get('m_info', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&amp;mode=post_details&amp;p=" . $row['post_id']) : '',
2063        'U_DELETE'            => ($delete_allowed) ? append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=' . (($softdelete_allowed) ? 'soft_delete' : 'delete') . "&amp;p={$row['post_id']}") : '',
2064
2065        'U_SEARCH'        => $user_cache[$poster_id]['search'],
2066        'U_PM'            => $u_pm,
2067        'U_EMAIL'        => $user_cache[$poster_id]['email'],
2068
2069        'U_APPROVE_ACTION'    => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&amp;p={$row['post_id']}&amp;f={$row['forum_id']}&amp;redirect=" . urlencode(str_replace('&amp;', '&', $viewtopic_url . '&amp;p=' . $row['post_id'] . '#p' . $row['post_id']))),
2070        'U_REPORT'            => ($auth->acl_get('f_report', $forum_id)) ? $phpbb_container->get('controller.helper')->route('phpbb_report_post_controller', array('id' => $row['post_id'])) : '',
2071        'U_MCP_REPORT'        => ($auth->acl_get('m_report', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&amp;mode=report_details&amp;p=' . $row['post_id']) : '',
2072        'U_MCP_APPROVE'        => ($auth->acl_get('m_approve', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&amp;mode=approve_details&amp;p=' . $row['post_id']) : '',
2073        'U_MCP_RESTORE'        => ($auth->acl_get('m_approve', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&amp;mode=' . (($topic_data['topic_visibility'] != ITEM_DELETED) ? 'deleted_posts' : 'deleted_topics') . '&amp;p=' . $row['post_id']) : '',
2074        'U_MINI_POST'        => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $row['post_id']) . '#p' . $row['post_id'],
2075        'U_MINI_POST_VIEW'    => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $row['post_id']) . '&amp;view=show#p' . $row['post_id'],
2076        'U_NEXT_POST_ID'    => ($i < $i_total && isset($rowset[$post_list[$i + 1]])) ? $rowset[$post_list[$i + 1]]['post_id'] : '',
2077        'U_PREV_POST_ID'    => $prev_post_id,
2078        'U_NOTES'            => ($auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&amp;mode=user_notes&amp;u=' . $poster_id) : '',
2079        'U_WARN'            => ($auth->acl_get('m_warn') && $poster_id != $user->data['user_id'] && $poster_id != ANONYMOUS) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&amp;mode=warn_post&amp;p=' . $row['post_id']) : '',
2080
2081        'POST_ID'            => $row['post_id'],
2082        'POST_NUMBER'        => $i + $start + 1,
2083        'POSTER_ID'            => $poster_id,
2084        'MINI_POST'            => ($post_unread) ? $user->lang['UNREAD_POST'] : $user->lang['POST'],
2085
2086
2087        'S_HAS_ATTACHMENTS'    => (!empty($attachments[$row['post_id']])) ? true : false,
2088        'S_MULTIPLE_ATTACHMENTS'    => !empty($attachments[$row['post_id']]) && count($attachments[$row['post_id']]) > 1,
2089        'S_POST_UNAPPROVED'    => ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE) ? true : false,
2090        'S_CAN_APPROVE'        => $auth->acl_get('m_approve', $forum_id),
2091        'S_POST_DELETED'    => ($row['post_visibility'] == ITEM_DELETED) ? true : false,
2092        'L_POST_DELETED_MESSAGE'    => $l_deleted_message,
2093        'S_POST_REPORTED'    => ($row['post_reported'] && $auth->acl_get('m_report', $forum_id)) ? true : false,
2094        'S_DISPLAY_NOTICE'    => $display_notice && $row['post_attachment'],
2095        'S_FRIEND'            => ($row['friend']) ? true : false,
2096        'S_UNREAD_POST'        => $post_unread,
2097        'S_FIRST_UNREAD'    => $s_first_unread,
2098        'S_CUSTOM_FIELDS'    => (isset($cp_row['row']) && count($cp_row['row'])) ? true : false,
2099        'S_TOPIC_POSTER'    => ($topic_data['topic_poster'] == $poster_id) ? true : false,
2100        'S_FIRST_POST'        => ($topic_data['topic_first_post_id'] == $row['post_id']) ? true : false,
2101
2102        'S_IGNORE_POST'        => ($row['foe']) ? true : false,
2103        'L_IGNORE_POST'        => ($row['foe']) ? sprintf($user->lang['POST_BY_FOE'], get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username'])) : '',
2104        'S_POST_HIDDEN'        => $row['hide_post'],
2105        'S_DELETE_PERMANENT'    => $permanent_delete_allowed,
2106    );
2107
2108    if ($user_cache[$poster_id]['avatar'])
2109    {
2110        $post_row += $avatar_helper->get_template_vars($user_cache[$poster_id]['avatar'], 'POSTER_');
2111    }
2112
2113    $user_poster_data = $user_cache[$poster_id];
2114
2115    $current_row_number = $i;
2116
2117    /**
2118    * Modify the posts template block
2119    *
2120    * @event core.viewtopic_modify_post_row
2121    * @var    int        start                Start item of this page
2122    * @var    int        current_row_number    Number of the post on this page
2123    * @var    int        end                    Number of posts on this page
2124    * @var    int        total_posts            Total posts count
2125    * @var    int        poster_id            Post author id
2126    * @var    array    row                    Array with original post and user data
2127    * @var    array    cp_row                Custom profile field data of the poster
2128    * @var    array    attachments            List of attachments
2129    * @var    array    user_poster_data    Poster's data from user cache
2130    * @var    array    post_row            Template block array of the post
2131    * @var    array    topic_data            Array with topic data
2132    * @var    array    user_cache            Array with cached user data
2133    * @var    array    post_edit_list        Array with post edited list
2134    * @since 3.1.0-a1
2135    * @changed 3.1.0-a3 Added vars start, current_row_number, end, attachments
2136    * @changed 3.1.0-b3 Added topic_data array, total_posts
2137    * @changed 3.1.0-RC3 Added poster_id
2138    * @changed 3.2.2-RC1 Added user_cache and post_edit_list
2139    */
2140    $vars = array(
2141        'start',
2142        'current_row_number',
2143        'end',
2144        'total_posts',
2145        'poster_id',
2146        'row',
2147        'cp_row',
2148        'attachments',
2149        'user_poster_data',
2150        'post_row',
2151        'topic_data',
2152        'user_cache',
2153        'post_edit_list',
2154    );
2155    extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_row', compact($vars)));
2156
2157    $i = $current_row_number;
2158
2159    if (isset($cp_row['row']) && count($cp_row['row']))
2160    {
2161        $post_row = array_merge($post_row, $cp_row['row']);
2162    }
2163
2164    // Dump vars into template
2165    $template->assign_block_vars('postrow', $post_row);
2166
2167    $contact_fields = array(
2168        array(
2169            'ID'        => 'pm',
2170            'NAME'         => $user->lang['SEND_PRIVATE_MESSAGE'],
2171            'U_CONTACT'    => $post_row['U_PM'],
2172        ),
2173        array(
2174            'ID'        => 'email',
2175            'NAME'        => $user->lang['SEND_EMAIL'],
2176            'U_CONTACT'    => $user_cache[$poster_id]['email'],
2177        ),
2178    );
2179
2180    foreach ($contact_fields as $field)
2181    {
2182        if ($field['U_CONTACT'])
2183        {
2184            $template->assign_block_vars('postrow.contact', $field);
2185        }
2186    }
2187
2188    if (!empty($cp_row['blockrow']))
2189    {
2190        foreach ($cp_row['blockrow'] as $field_data)
2191        {
2192            $template->assign_block_vars('postrow.custom_fields', $field_data);
2193
2194            if ($field_data['S_PROFILE_CONTACT'])
2195            {
2196                $template->assign_block_vars('postrow.contact', array(
2197                    'ID'        => $field_data['PROFILE_FIELD_IDENT'],
2198                    'NAME'        => $field_data['PROFILE_FIELD_NAME'],
2199                    'U_CONTACT'    => $field_data['PROFILE_FIELD_CONTACT'],
2200                ));
2201            }
2202        }
2203    }
2204
2205    // Display not already displayed Attachments for this post, we already parsed them. ;)
2206    if (!empty($attachments[$row['post_id']]))
2207    {
2208        foreach ($attachments[$row['post_id']] as $attachment)
2209        {
2210            $template->assign_block_vars('postrow.attachment', array(
2211                'DISPLAY_ATTACHMENT'    => $attachment)
2212            );
2213        }
2214    }
2215
2216    $current_row_number = $i;
2217
2218    /**
2219    * Event after the post data has been assigned to the template
2220    *
2221    * @event core.viewtopic_post_row_after
2222    * @var    int        start                Start item of this page
2223    * @var    int        current_row_number    Number of the post on this page
2224    * @var    int        end                    Number of posts on this page
2225    * @var    int        total_posts            Total posts count
2226    * @var    array    row                    Array with original post and user data
2227    * @var    array    cp_row                Custom profile field data of the poster
2228    * @var    array    attachments            List of attachments
2229    * @var    array    user_poster_data    Poster's data from user cache
2230    * @var    array    post_row            Template block array of the post
2231    * @var    array    topic_data            Array with topic data
2232    * @since 3.1.0-a3
2233    * @changed 3.1.0-b3 Added topic_data array, total_posts
2234    */
2235    $vars = array(
2236        'start',
2237        'current_row_number',
2238        'end',
2239        'total_posts',
2240        'row',
2241        'cp_row',
2242        'attachments',
2243        'user_poster_data',
2244        'post_row',
2245        'topic_data',
2246    );
2247    extract($phpbb_dispatcher->trigger_event('core.viewtopic_post_row_after', compact($vars)));
2248
2249    $i = $current_row_number;
2250
2251    $prev_post_id = $row['post_id'];
2252
2253    unset($rowset[$post_list[$i]]);
2254    unset($attachments[$row['post_id']]);
2255}
2256unset($rowset, $user_cache);
2257
2258// Update topic view and if necessary attachment view counters ... but only for humans and if this is the first 'page view'
2259if (isset($user->data['session_page']) && !$user->data['is_bot'] && (strpos($user->data['session_page'], '&t=' . $topic_id) === false || isset($user->data['session_created'])))
2260{
2261    $sql = 'UPDATE ' . TOPICS_TABLE . '
2262        SET topic_views = topic_views + 1, topic_last_view_time = ' . time() . "
2263        WHERE topic_id = $topic_id";
2264    $db->sql_query($sql);
2265
2266    // Update the attachment download counts
2267    if (count($update_count))
2268    {
2269        $sql = 'UPDATE ' . ATTACHMENTS_TABLE . '
2270            SET download_count = download_count + 1
2271            WHERE ' . $db->sql_in_set('attach_id', array_unique($update_count));
2272        $db->sql_query($sql);
2273    }
2274}
2275
2276// Only mark topic if it's currently unread. Also make sure we do not set topic tracking back if earlier pages are viewed.
2277if (isset($topic_tracking_info[$topic_id]) && $topic_data['topic_last_post_time'] > $topic_tracking_info[$topic_id] && $max_post_time > $topic_tracking_info[$topic_id])
2278{
2279    markread('topic', $forum_id, $topic_id, $max_post_time);
2280
2281    // Update forum info
2282    $all_marked_read = update_forum_tracking_info($forum_id, $topic_data['forum_last_post_time'], (isset($topic_data['forum_mark_time'])) ? $topic_data['forum_mark_time'] : false, false);
2283}
2284else
2285{
2286    $all_marked_read = true;
2287}
2288
2289// If there are absolutely no more unread posts in this forum
2290// and unread posts shown, we can safely show the #unread link
2291if ($all_marked_read)
2292{
2293    if ($post_unread)
2294    {
2295        $template->assign_vars(array(
2296            'U_VIEW_UNREAD_POST'    => '#unread',
2297        ));
2298    }
2299    else if (isset($topic_tracking_info[$topic_id]) && $topic_data['topic_last_post_time'] > $topic_tracking_info[$topic_id])
2300    {
2301        $template->assign_vars(array(
2302            'U_VIEW_UNREAD_POST'    => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id&amp;view=unread") . '#unread',
2303        ));
2304    }
2305}
2306else if (!$all_marked_read)
2307{
2308    $last_page = ((floor($start / $config['posts_per_page']) + 1) == max(ceil($total_posts / $config['posts_per_page']), 1)) ? true : false;
2309
2310    // What can happen is that we are at the last displayed page. If so, we also display the #unread link based in $post_unread
2311    if ($last_page && $post_unread)
2312    {
2313        $template->assign_vars(array(
2314            'U_VIEW_UNREAD_POST'    => '#unread',
2315        ));
2316    }
2317    else if (!$last_page)
2318    {
2319        $template->assign_vars(array(
2320            'U_VIEW_UNREAD_POST'    => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id&amp;view=unread") . '#unread',
2321        ));
2322    }
2323}
2324
2325// let's set up quick_reply
2326$s_quick_reply = false;
2327if ($user->data['is_registered'] && $config['allow_quick_reply'] && ($topic_data['forum_flags'] & FORUM_FLAG_QUICK_REPLY) && $auth->acl_get('f_reply', $forum_id))
2328{
2329    // Quick reply enabled forum
2330    $s_quick_reply = (($topic_data['forum_status'] == ITEM_UNLOCKED && $topic_data['topic_status'] == ITEM_UNLOCKED) || $auth->acl_get('m_edit', $forum_id)) ? true : false;
2331}
2332
2333if ($s_can_vote || $s_quick_reply)
2334{
2335    add_form_key('posting');
2336
2337    if ($s_quick_reply)
2338    {
2339        $s_attach_sig    = $config['allow_sig'] && $user->optionget('attachsig') && $auth->acl_get('f_sigs', $forum_id) && $auth->acl_get('u_sig');
2340        $s_smilies        = $config['allow_smilies'] && $user->optionget('smilies') && $auth->acl_get('f_smilies', $forum_id);
2341        $s_bbcode        = $config['allow_bbcode'] && $user->optionget('bbcode') && $auth->acl_get('f_bbcode', $forum_id);
2342        $s_notify        = $config['allow_topic_notify'] && ($user->data['user_notify'] || $s_watching_topic['is_watching']);
2343
2344        $qr_hidden_fields = array(
2345            'topic_cur_post_id'        => (int) $topic_data['topic_last_post_id'],
2346            'topic_id'                => (int) $topic_data['topic_id'],
2347            'forum_id'                => (int) $forum_id,
2348        );
2349
2350        // Originally we use checkboxes and check with isset(), so we only provide them if they would be checked
2351        (!$s_bbcode)                    ? $qr_hidden_fields['disable_bbcode'] = 1        : true;
2352        (!$s_smilies)                    ? $qr_hidden_fields['disable_smilies'] = 1        : true;
2353        (!$config['allow_post_links'])    ? $qr_hidden_fields['disable_magic_url'] = 1    : true;
2354        ($s_attach_sig)                    ? $qr_hidden_fields['attach_sig'] = 1            : true;
2355        ($s_notify)                        ? $qr_hidden_fields['notify'] = 1                : true;
2356        ($topic_data['topic_status'] == ITEM_LOCKED) ? $qr_hidden_fields['lock_topic'] = 1 : true;
2357
2358        $tpl_ary = [
2359            'S_QUICK_REPLY'            => true,
2360            'U_QR_ACTION'            => append_sid("{$phpbb_root_path}posting.$phpEx", "mode=reply&amp;t=$topic_id"),
2361            'QR_HIDDEN_FIELDS'        => build_hidden_fields($qr_hidden_fields),
2362            'SUBJECT'                => 'Re: ' . censor_text($topic_data['topic_title']),
2363        ];
2364
2365        /**
2366        * Event after the quick-reply has been setup
2367        *
2368        * @event core.viewtopic_modify_quick_reply_template_vars
2369        * @var    array    tpl_ary            Array with template data
2370        * @var    array    topic_data        Array with topic data
2371        * @since 3.2.9-RC1
2372        */
2373        $vars = ['tpl_ary', 'topic_data'];
2374        extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_quick_reply_template_vars', compact($vars)));
2375
2376        $template->assign_vars($tpl_ary);
2377    }
2378}
2379// now I have the urge to wash my hands :(
2380
2381
2382// We overwrite $_REQUEST['f'] if there is no forum specified
2383// to be able to display the correct online list.
2384// One downside is that the user currently viewing this topic/post is not taken into account.
2385if (!$request->variable('f', 0))
2386{
2387    $request->overwrite('f', $forum_id);
2388}
2389
2390// We need to do the same with the topic_id. See #53025.
2391if (!$request->variable('t', 0) && !empty($topic_id))
2392{
2393    $request->overwrite('t', $topic_id);
2394}
2395
2396$page_title = $topic_data['topic_title'] . ($start ? ' - ' . sprintf($user->lang['PAGE_TITLE_NUMBER'], $pagination->get_on_page($config['posts_per_page'], $start)) : '');
2397
2398/**
2399* You can use this event to modify the page title of the viewtopic page
2400*
2401* @event core.viewtopic_modify_page_title
2402* @var    string    page_title        Title of the viewtopic page
2403* @var    array    topic_data        Array with topic data
2404* @var    int        forum_id        Forum ID of the topic
2405* @var    int        start            Start offset used to calculate the page
2406* @var    array    post_list        Array with post_ids we are going to display
2407* @since 3.1.0-a1
2408* @changed 3.1.0-RC4 Added post_list var
2409*/
2410$vars = array('page_title', 'topic_data', 'forum_id', 'start', 'post_list');
2411extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_page_title', compact($vars)));
2412
2413// Output the page
2414page_header($page_title, true, $forum_id);
2415
2416$template->set_filenames(array(
2417    'body' => ($view == 'print') ? 'viewtopic_print.html' : 'viewtopic_body.html')
2418);
2419make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"), $forum_id);
2420
2421page_footer();