<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2016

 See text/EN/licence.txt for full licencing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  ocProducts Ltd
 * @package    cns_forum
 */

/**
 * Find the URL to a post.
 *
 * @param  AUTO_LINK $post_id The post ID.
 * @return URLPATH The URL.
 */
function find_post_id_url($post_id)
{
    $max = intval(get_option('forum_posts_per_page'));
    if ($max == 0) {
        $max = 1;
    }

    $id = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_posts', 'p_topic_id', array('id' => $post_id));
    if (is_null($id)) {
        warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'post'));
    }

    // What page is it on?
    $before = $GLOBALS['FORUM_DB']->query_value_if_there('SELECT COUNT(*) FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_posts WHERE id<' . strval($post_id) . ' AND ' . cns_get_topic_where($id), false, true);
    $start = intval(floor(floatval($before) / floatval($max))) * $max;

    // Now redirect accordingly
    $map = array('page' => 'topicview', 'type' => null, 'id' => $id, 'topic_start' => ($start == 0) ? null : $start, 'post_id' => $post_id, 'threaded' => get_param_integer('threaded', null));
    foreach ($_GET as $key => $val) {
        if ((substr($key, 0, 3) == 'kfs') || (in_array($key, array('topic_start', 'topic_max')))) {
            $map[$key] = $val;
        }
    }
    $test_threaded = get_param_integer('threaded', null);
    if ($test_threaded !== null) {
        $map['threaded'] = $test_threaded;
    }
    $_redirect = build_url($map, '_SELF', null, true);
    $redirect = $_redirect->evaluate();
    $redirect .= '#post_' . strval($post_id);

    return $redirect;
}

/**
 * Find the URL to the latest unread post in a topic.
 *
 * @param  AUTO_LINK $id The topic ID.
 * @return URLPATH The URL.
 */
function find_first_unread_url($id)
{
    $max = intval(get_option('forum_posts_per_page'));
    if ($max == 0) {
        $max = 1;
    }

    $last_read_time = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_read_logs', 'l_time', array('l_member_id' => get_member(), 'l_topic_id' => $id));
    if (is_null($last_read_time)) {
        // Assumes that everything made in the last two weeks has not been read
        $unread_details = $GLOBALS['FORUM_DB']->query('SELECT id,p_time FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_posts WHERE p_topic_id=' . strval($id) . ' AND p_time>' . strval(time() - 60 * 60 * 24 * intval(get_option('post_read_history_days'))) . ' ORDER BY id', 1);
        if (array_key_exists(0, $unread_details)) {
            $last_read_time = $unread_details[0]['p_time'] - 1;
        } else {
            $last_read_time = 0;
        }
    }
    $first_unread_id = $GLOBALS['FORUM_DB']->query_value_if_there('SELECT id FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_posts WHERE p_topic_id=' . strval($id) . ' AND p_time>' . strval($last_read_time) . ' ORDER BY id');
    if (!is_null($first_unread_id)) {
        // What page is it on?
        $before = $GLOBALS['FORUM_DB']->query_value_if_there('SELECT COUNT(*) FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_posts WHERE id<' . strval($first_unread_id) . ' AND ' . cns_get_topic_where($id), false, true);
        $start = intval(floor(floatval($before) / floatval($max))) * $max;
    } else {
        $first_unread_id = -2;

        // What page is it on?
        $before = $GLOBALS['FORUM_DB']->query_value_if_there('SELECT COUNT(*) FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_posts WHERE ' . cns_get_topic_where($id), false, true);
        $start = intval(floor(floatval($before) / floatval($max))) * $max;
        if ($start == $before) {
            $start = max(0, $before - $max);
        }
    }

    // Now redirect accordingly
    $map = array('page' => 'topicview', 'id' => $id, 'type' => null, 'topic_start' => ($start == 0) ? null : $start, 'post_id' => $first_unread_id);
    foreach ($_GET as $key => $val) {
        if ((substr($key, 0, 3) == 'kfs') || (in_array($key, array('topic_start', 'topic_max')))) {
            $map[$key] = $val;
        }
    }
    $_redirect = build_url($map, '_SELF', null, true);
    $redirect = $_redirect->evaluate();
    if ($first_unread_id > 0) {
        $redirect .= '#post_' . strval($first_unread_id);
    } else {
        $redirect .= '#first_unread';
    }

    return $redirect;
}

/**
 * Turn a post row, into a detailed map of information that is suitable for use as display parameters for that post.
 *
 * @param  array $_postdetails The post row.
 * @param  array $topic_info The topic row.
 * @param  boolean $only_post Whether the post is the only post in the topic.
 * @return array The detailed map.
 */
function cns_get_details_to_show_post($_postdetails, $topic_info, $only_post = false)
{
    $forum_id = $_postdetails['p_cache_forum_id'];

    $primary_group = cns_get_member_primary_group($_postdetails['p_poster']);
    if (is_null($primary_group)) {
        $_postdetails['p_poster'] = db_get_first_id();
        $primary_group = db_get_first_id();
    }

    $post = array(
        'id' => $_postdetails['id'],
        'topic_id' => $_postdetails['p_topic_id'],
        'title' => $_postdetails['p_title'],
        'post' => $_postdetails['message'],
        'time' => $_postdetails['p_time'],
        'time_string' => get_timezoned_date($_postdetails['p_time']),
        'validated' => $_postdetails['p_validated'],
        'is_emphasised' => $_postdetails['p_is_emphasised'],
        'poster_username' => $_postdetails['p_poster_name_if_guest'],
        'poster' => $_postdetails['p_poster'],
    );

    $post['has_revisions'] = false;
    if (addon_installed('actionlog')) {
        require_code('revisions_engine_database');
        $revision_engine = new RevisionEngineDatabase(true);
        if ($revision_engine->has_revisions(array('post'), strval($_postdetails['id']))) {
            $post['has_revisions'] = true;
        }
    }

    if (array_key_exists('message_comcode', $_postdetails)) {
        $post['message_comcode'] = $_postdetails['message_comcode'];
    }

    // Edited?
    if (!is_null($_postdetails['p_last_edit_by'])) {
        $post['last_edit_by'] = $_postdetails['p_last_edit_by'];
        $post['last_edit_time'] = $_postdetails['p_last_edit_time'];
        $post['last_edit_time_string'] = get_timezoned_date($_postdetails['p_last_edit_time']);
        $post['last_edit_by_username'] = $GLOBALS['CNS_DRIVER']->get_username($_postdetails['p_last_edit_by']);
        if ($post['last_edit_by_username'] == '') {
            $post['last_edit_by_username'] = do_lang('UNKNOWN'); // Shouldn't happen, but imported data can be weird
        }
    }

    // Find title
    $title = addon_installed('cns_member_titles') ? $GLOBALS['CNS_DRIVER']->get_member_row_field($_postdetails['p_poster'], 'm_title') : '';
    if ($title == '') {
        $title = get_translated_text(cns_get_group_property($primary_group, 'title'), $GLOBALS['FORUM_DB']);
    }
    $post['poster_title'] = $title;

    // If this isn't guest posted, we can put some member details in
    if ((!is_null($_postdetails['p_poster'])) && ($_postdetails['p_poster'] != $GLOBALS['CNS_DRIVER']->get_guest_id())) {
        if (addon_installed('points')) {
            require_code('points');
            $post['poster_points'] = total_points($_postdetails['p_poster']);
        }
        $post['poster_posts'] = $GLOBALS['CNS_DRIVER']->get_member_row_field($_postdetails['p_poster'], 'm_cache_num_posts');
        $post['poster_highlighted_name'] = $GLOBALS['CNS_DRIVER']->get_member_row_field($_postdetails['p_poster'], 'm_highlighted_name');

        // Signature
        if ((($GLOBALS['CNS_DRIVER']->get_member_row_field(get_member(), 'm_views_signatures') == 1) || (get_option('enable_views_sigs_option', true) === '0')) && ($_postdetails['p_skip_sig'] == 0) && (addon_installed('cns_signatures'))) {
            global $SIGNATURES_CACHE;
            if (array_key_exists($_postdetails['p_poster'], $SIGNATURES_CACHE)) {
                $sig = $SIGNATURES_CACHE[$_postdetails['p_poster']];
            } else {
                $member_row = $GLOBALS['CNS_DRIVER']->get_member_row($_postdetails['p_poster']);
                $just_member_row = db_map_restrict($member_row, array('id', 'm_signature'));
                $sig = get_translated_tempcode('f_members', $just_member_row, 'm_signature', $GLOBALS['FORUM_DB']);
                $SIGNATURES_CACHE[$_postdetails['p_poster']] = $sig;
            }
            $post['signature'] = $sig;
        }

        // Any custom fields to show?
        $post['custom_fields'] = cns_get_all_custom_fields_match_member(
            $_postdetails['p_poster'],
            ((get_member() != $_postdetails['p_poster']) && (!has_privilege(get_member(), 'view_any_profile_field'))) ? 1 : null,
            ((get_member() == $_postdetails['p_poster']) && (!has_privilege(get_member(), 'view_any_profile_field'))) ? 1 : null,
            null,
            null,
            null,
            1
        );

        // Usergroup
        $post['primary_group'] = $primary_group;
        $post['primary_group_name'] = cns_get_group_name($primary_group);

        // Find avatar
        $avatar = $GLOBALS['CNS_DRIVER']->get_member_avatar_url($_postdetails['p_poster']);
        if ($avatar != '') {
            $post['poster_avatar'] = $avatar;
        }

        // Any warnings?
        if ((has_privilege(get_member(), 'see_warnings')) && (addon_installed('cns_warnings'))) {
            $num_warnings = $GLOBALS['CNS_DRIVER']->get_member_row_field($_postdetails['p_poster'], 'm_cache_warnings');
            $post['poster_num_warnings'] = $num_warnings;
        }

        // Join date
        $post['poster_join_date'] = $GLOBALS['CNS_DRIVER']->get_member_row_field($_postdetails['p_poster'], 'm_join_time');
        $post['poster_join_date_string'] = get_timezoned_date($post['poster_join_date']);
    } elseif ($_postdetails['p_poster'] == $GLOBALS['CNS_DRIVER']->get_guest_id()) {
        if ($_postdetails['p_poster_name_if_guest'] == do_lang('SYSTEM')) {
            $post['poster_avatar'] = find_theme_image('cns_default_avatars/system', true);
            $post['poster_title'] = '';
        }
    }

    // Do we have any special controls over this post?
    require_code('cns_posts');
    $reason = null;
    $may_edit = cns_may_edit_post_by($_postdetails['id'], $_postdetails['p_time'], $_postdetails['p_poster'], $forum_id, get_member(), $topic_info['t_is_open'] == 0, $reason);
    if ($may_edit || $reason !== null/*Interesting reason, let them find it out when they click*/) {
        $post['may_edit'] = true;
    }
    if (!$only_post) {
        $may_delete = cns_may_delete_post_by($_postdetails['id'], $_postdetails['p_time'], $_postdetails['p_poster'], $forum_id, get_member(), $reason);
        if ($may_delete || $reason !== null/*Interesting reason, let them find it out when they click*/) {
            $post['may_delete'] = true;
        }
    }

    // More
    if (has_privilege(get_member(), 'see_ip')) {
        $post['ip_address'] = $_postdetails['p_ip_address'];
    }
    if (!is_null($_postdetails['p_intended_solely_for'])) {
        $post['intended_solely_for'] = $_postdetails['p_intended_solely_for'];
    }

    return $post;
}

/**
 * Read in a great big map of details relating to a topic.
 *
 * @param  ?AUTO_LINK $topic_id The ID of the topic we are getting details of (null: whispers).
 * @param  integer $start The start row for getting details of posts in the topic (i.e. 0 is start of topic, higher is further through).
 * @param  integer $max The maximum number of posts to get detail of.
 * @param  boolean $view_poll_results Whether we are viewing poll results for the topic (if there is no poll for the topic, this is irrelevant).
 * @param  boolean $check_perms Whether to check permissions.
 * @return array The map of details.
 */
function cns_read_in_topic($topic_id, $start, $max, $view_poll_results = false, $check_perms = true)
{
    if (!is_null($topic_id)) {
        $table = 'f_topics t LEFT JOIN ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_forums f ON f.id=t.t_forum_id';
        $select = array('t.*', 'f.f_is_threaded');
        if (multi_lang_content()) {
            $select[] = 't_cache_first_post AS p_post';
        } else {
            $table .= ' LEFT JOIN ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_posts p ON p.id=t.t_cache_first_post_id';
            $select[] = 'p_post,p_post__text_parsed,p_post__source_user';
        }
        $_topic_info = $GLOBALS['FORUM_DB']->query_select($table, $select, array('t.id' => $topic_id), '', 1);
        if (!array_key_exists(0, $_topic_info)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'topic'));
        }
        $topic_info = $_topic_info[0];

        // Are we allowed into here?
        //  Check forum
        $forum_id = $topic_info['t_forum_id'];
        if (!is_null($forum_id)) {
            if ($check_perms) {
                if (!has_category_access(get_member(), 'forums', strval($forum_id))) {
                    access_denied('CATEGORY_ACCESS_LEVEL');
                }
            }
        } else {
            // It must be a private topic. Do we have access?
            $from = $topic_info['t_pt_from'];
            $to = $topic_info['t_pt_to'];

            if (($from != get_member()) && ($to != get_member()) && (!cns_has_special_pt_access($topic_id)) && (!has_privilege(get_member(), 'view_other_pt'))) {
                access_denied('PRIVILEGE', 'view_other_pt');
            }

            decache(array(
                array('side_cns_private_topics', array(get_member())),
                array('_new_pp', array(get_member())),
            ));
        }
        // Check validated
        if (($topic_info['t_validated'] == 0) && (addon_installed('unvalidated'))) {
            if ((!has_privilege(get_member(), 'jump_to_unvalidated')) && ($check_perms) && ((is_guest()) || ($topic_info['t_cache_first_member_id'] != get_member()))) {
                access_denied('PRIVILEGE', 'jump_to_unvalidated');
            }
        }

        if (is_null(get_param_integer('threaded', null))) {
            if ($start > 0) {
                if ($topic_info['f_is_threaded'] == 1) {
                    $_GET['threaded'] = '0';
                }
            }
        }
        $is_threaded = get_param_integer('threaded', (is_null($topic_info['f_is_threaded']) ? 0 : $topic_info['f_is_threaded']));
        if ($is_threaded != 1) {
            $is_threaded = 0; // In case of invalid URLs causing inconsistent handling
        }

        // Some general info
        require_code('seo2');
        list(, $meta_description) = _seo_meta_find_data(array(), get_translated_text($topic_info['p_post']));
        $out = array(
            'num_views' => $topic_info['t_num_views'],
            'num_posts' => $topic_info['t_cache_num_posts'],
            'validated' => $topic_info['t_validated'],
            'title' => $topic_info['t_cache_first_title'],
            'description' => $topic_info['t_description'],
            'description_link' => $topic_info['t_description_link'],
            'emoticon' => $topic_info['t_emoticon'],
            'forum_id' => $topic_info['t_forum_id'],
            'first_post' => $topic_info['p_post'],
            'first_poster' => $topic_info['t_cache_first_member_id'],
            'first_post_id' => $topic_info['t_cache_first_post_id'],
            'pt_from' => $topic_info['t_pt_from'],
            'pt_to' => $topic_info['t_pt_to'],
            'is_open' => $topic_info['t_is_open'],
            'is_threaded' => $is_threaded,
            'is_really_threaded' => is_null($topic_info['f_is_threaded']) ? 0 : $topic_info['f_is_threaded'],
            'last_time' => $topic_info['t_cache_last_time'],
            'metadata' => array(
                'identifier' => '_SEARCH:topicview:browse:' . strval($topic_id),
                'numcomments' => strval($topic_info['t_cache_num_posts']),
                'description' => $meta_description, // There's no meta description, so we'll take this as a description, which will feed through
            ),
            'row' => $topic_info,
        );

        // Poll?
        if (!is_null($topic_info['t_poll_id'])) {
            require_code('cns_polls');
            if (is_guest()) {
                $voted_already_map = array('pv_poll_id' => $topic_info['t_poll_id'], 'pv_ip' => get_ip_address());
            } else {
                $voted_already_map = array('pv_poll_id' => $topic_info['t_poll_id'], 'pv_member_id' => get_member());
            }
            $voted_already = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_poll_votes', 'pv_member_id', $voted_already_map);
            $test = cns_poll_get_results($topic_info['t_poll_id'], $view_poll_results || (!is_null($voted_already)));
            if ($test !== null) {
                $out['poll'] = $test;
                $out['poll']['voted_already'] = $voted_already;
                $out['poll_id'] = $topic_info['t_poll_id'];
            }
        }

        // Post query
        $where = cns_get_topic_where($topic_id);
        $query = 'SELECT p.* FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_posts p WHERE ' . $where . ' ORDER BY p_time,p.id';
    } else {
        $out = array(
            'num_views' => 0,
            'num_posts' => 0,
            'validated' => 1,
            'title' => do_lang('INLINE_PERSONAL_POSTS'),
            'description' => '',
            'description_link' => '',
            'emoticon' => '',
            'forum_id' => null,
            'first_post' => null,
            'first_poster' => null,
            'first_post_id' => null,
            'pt_from' => null,
            'pt_to' => null,
            'is_open' => 1,
            'is_threaded' => 0,
            'last_time' => time(),
            'metadata' => array(),
            'row' => array(),
        );

        // Post query
        $where = 'p_intended_solely_for=' . strval(get_member());
        $query = 'SELECT p.* FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_posts p WHERE ' . $where . ' ORDER BY p_time,p.id';

        $topic_info = array(
            't_is_open' => 1,
        );
    }

    // Posts
    if ($out['is_threaded'] == 0) {
        if ($start < 200) {
            $_postdetailss = list_to_map('id', $GLOBALS['FORUM_DB']->query($query, $max, $start, false, false, array('p_post' => 'LONG_TRANS__COMCODE')));
        } else { // deep search, so we need to make offset more efficient, trade-off is more queries
            $_postdetailss = list_to_map('id', $GLOBALS['FORUM_DB']->query($query, $max, $start));
        }
        if (($start == 0) && (count($_postdetailss) < $max)) {
            $out['max_rows'] = $max; // We know that they're all on this screen
        } else {
            $out['max_rows'] = (is_null($topic_id) || $topic_info['t_cache_num_posts'] < 500) ? $GLOBALS['FORUM_DB']->query_value_if_there('SELECT COUNT(*) FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_posts WHERE ' . $where) : $topic_info['t_cache_num_posts']/*for performance reasons*/;
        }
        $posts = array();
        // Precache member/group details in one fell swoop
        $members = array();
        foreach ($_postdetailss as $_postdetails) {
            $members[$_postdetails['p_poster']] = 1;
            if ($out['title'] == '') {
                $out['title'] = $_postdetails['p_title'];
            }
        }
        cns_cache_member_details(array_keys($members));

        $i = 0;
        foreach ($_postdetailss as $_postdetails) {
            $_postdetails['message_comcode'] = get_translated_text($_postdetails['p_post'], $GLOBALS['FORUM_DB']);

            $linked_type = '';
            $linked_id = '';
            $linked_url = '';

            // If it's a spacer post, see if we can detect it better
            $is_spacer_post = (($i == 0) && (substr($_postdetails['message_comcode'], 0, strlen('[semihtml]' . do_lang('SPACER_POST_MATCHER'))) == '[semihtml]' . do_lang('SPACER_POST_MATCHER')));
            if ($is_spacer_post) {
                $c_prefix = do_lang('COMMENT') . ': #';
                if ((substr($out['description'], 0, strlen($c_prefix)) == $c_prefix) && ($out['description_link'] != '')) {
                    list($linked_type, $linked_id) = explode('_', substr($out['description'], strlen($c_prefix)), 2);
                    $linked_url = $out['description_link'];
                    $out['description'] = '';
                }
            }

            // Load post
            $post_row = db_map_restrict($_postdetails, array('id', 'p_post'));
            $_postdetails['message'] = get_translated_tempcode('f_posts', $post_row, 'p_post', $GLOBALS['FORUM_DB']);

            // Fake a quoted post? (kind of a nice 'tidy up' feature if a forum's threading has been turned off, leaving things for flat display)
            if ((!is_null($_postdetails['p_parent_id'])) && (strpos($_postdetails['message_comcode'], '[quote') === false)) {
                $p = mixed(); // null
                if (array_key_exists($_postdetails['p_parent_id'], $_postdetailss)) { // Ah, we're already loading it on this page
                    $p = $_postdetailss[$_postdetails['p_parent_id']];

                    // Load post
                    $_p = db_map_restrict($p, array('id', 'p_post'));
                    $p['message'] = get_translated_tempcode('f_posts', $_p, 'p_post', $GLOBALS['FORUM_DB']);
                } else { // Drat, we need to load it
                    $_p = $GLOBALS['FORUM_DB']->query_select('f_posts', array('*'), array('id' => $_postdetails['p_parent_id']), '', 1);
                    if (array_key_exists(0, $_p)) {
                        $p = $_p[0];
                        $p['message'] = get_translated_tempcode('f_posts', $p, 'p_post', $GLOBALS['FORUM_DB']);
                    }
                }
                $temp = $_postdetails['message'];
                $_postdetails['message'] = new Tempcode();
                $_postdetails['message'] = do_template('COMCODE_QUOTE_BY', array('_GUID' => '4521bfe295b1834460f498df488ee7cb', 'SAIDLESS' => false, 'BY' => $p['p_poster_name_if_guest'], 'CONTENT' => $p['message']));
                $_postdetails['message']->attach($temp);
            }

            // Spacer posts may have a better first post put in place
            if ($is_spacer_post) {
                require_code('cns_posts');
                list($new_description, $new_post) = cns_display_spacer_post($linked_type, $linked_id);
                //if (!is_null($new_description)) $out['description']=$new_description;   Actually, it's a bit redundant
                if (!is_null($new_post)) {
                    $_postdetails['message'] = $new_post;
                }

                $is_ticket = false;
                if (addon_installed('tickets')) {
                    require_code('tickets');
                    if (is_ticket_forum($forum_id)) {
                        $is_ticket = true;
                    }
                }
                if ($is_ticket) {
                    require_lang('tickets');
                    require_code('feedback');
                    $ticket_id = extract_topic_identifier($out['description']);
                    $ticket_type_id = $GLOBALS['SITE_DB']->query_select_value_if_there('tickets', 'ticket_type', array('ticket_id' => $ticket_id));
                    $ticket_type_name = mixed();
                    if (!is_null($ticket_type_id)) {
                        $ticket_type_name = $GLOBALS['SITE_DB']->query_select_value_if_there('ticket_types', 'ticket_type_name', array('id' => $ticket_id));
                    }
                    $out['title'] = do_lang_tempcode('_VIEW_SUPPORT_TICKET', escape_html($out['title']), is_null($ticket_type_name) ? do_lang('UNKNOWN') : escape_html(get_translated_text($ticket_type_name)));
                    $_postdetails['p_title'] = '';
                } else {
                    $out['title'] = do_lang_tempcode('SPACER_TOPIC_TITLE_WRAP', escape_html($out['title']));
                    $_postdetails['p_title'] = do_lang('SPACER_TOPIC_TITLE_WRAP', $_postdetails['p_title']);
                }
            }

            // Put together
            $collated_post_details = cns_get_details_to_show_post($_postdetails, $topic_info, ($start == 0) && (count($_postdetailss) == 1));
            $collated_post_details['is_spacer_post'] = $is_spacer_post;
            $posts[] = $collated_post_details;

            $i++;
        }

        $out['posts'] = $posts;
    }

    // Any special topic/for-any-post-in-topic controls?
    if (!is_null($topic_id)) {
        $out['last_poster'] = $topic_info['t_cache_last_member_id'];
        $out['last_post_id'] = $topic_info['t_cache_last_post_id'];
        if (cns_may_post_in_topic($forum_id, $topic_id, $topic_info['t_cache_last_member_id'], $topic_info['t_is_open'] == 0)) {
            $out['may_reply'] = true;
        }
        if (cns_may_post_in_topic($forum_id, $topic_id, $topic_info['t_cache_last_member_id'], $topic_info['t_is_open'] == 0, null, true)) {
            $out['may_reply_private_post'] = true;
        }
        if (cns_may_report_post()) {
            $out['may_report_posts'] = true;
        }
        if (cns_may_make_private_topic()) {
            $out['may_pt_members'] = true;
        }
        if (cns_may_edit_topics_by($forum_id, get_member(), $topic_info['t_cache_first_member_id'])) {
            $out['may_edit_topic'] = true;
        }
        require_code('cns_moderation');
        require_code('cns_forums');
        if (cns_may_warn_members()) {
            $out['may_warn_members'] = true;
        }
        if (cns_may_delete_topics_by($forum_id, get_member(), $topic_info['t_cache_first_member_id'])) {
            $out['may_delete_topic'] = true;
        }
        if (cns_may_perform_multi_moderation($forum_id)) {
            $out['may_multi_moderate'] = true;
        }
        if (has_privilege(get_member(), 'use_quick_reply')) {
            $out['may_use_quick_reply'] = true;
        }
        $may_moderate_forum = cns_may_moderate_forum($forum_id);
        if ($may_moderate_forum) {
            if ($topic_info['t_is_open'] == 0) {
                $out['may_open_topic'] = true;
            } else {
                $out['may_close_topic'] = true;
            }
            if ($topic_info['t_pinned'] == 0) {
                $out['may_pin_topic'] = true;
            } else {
                $out['may_unpin_topic'] = true;
            }
            if ($topic_info['t_sunk'] == 0) {
                $out['may_sink_topic'] = true;
            } else {
                $out['may_unsink_topic'] = true;
            }
            if ($topic_info['t_cascading'] == 0) {
                $out['may_cascade_topic'] = true;
            } else {
                $out['may_uncascade_topic'] = true;
            }
            $out['may_move_topic'] = true;
            $out['may_move_posts'] = true;
            $out['may_delete_posts'] = true;
            $out['may_validate_posts'] = true;
            $out['may_make_private'] = true;
            $out['may_change_max'] = true;
        } else {
            if (($topic_info['t_cache_first_member_id'] == get_member()) && (has_privilege(get_member(), 'close_own_topics')) && ($topic_info['t_is_open'] == 1)) {
                $out['may_close_topic'] = true;
            }
        }
        if (!is_null($topic_info['t_poll_id'])) {
            require_code('cns_polls');

            if (cns_may_edit_poll_by($forum_id, $topic_info['t_cache_first_member_id'])) {
                $out['may_edit_poll'] = true;
            }
            if (cns_may_delete_poll_by($forum_id, $topic_info['t_cache_first_member_id'])) {
                $out['may_delete_poll'] = true;
            }
        } else {
            require_code('cns_polls');

            if (cns_may_attach_poll($topic_id, $topic_info['t_cache_first_member_id'], !is_null($topic_info['t_poll_id']), $forum_id)) {
                $out['may_attach_poll'] = true;
            }
        }
    } else {
        $out['last_poster'] = null;
        $out['last_post_id'] = null;
    }

    return $out;
}

/**
 * Mass-load details for a list of members into memory, to reduce queries when we access it later.
 *
 * @param  array $members List of members.
 */
function cns_cache_member_details($members)
{
    require_code('cns_members');

    $member_or_list = '';
    foreach ($members as $member) {
        if ($member_or_list != '') {
            $member_or_list .= ' OR ';
        }
        $member_or_list .= 'm.id=' . strval($member);
    }
    if ($member_or_list != '') {
        global $TABLE_LANG_FIELDS_CACHE;
        $member_rows = $GLOBALS['FORUM_DB']->query('SELECT m.* FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_members m WHERE ' . $member_or_list, null, null, false, true);
        global $TABLE_LANG_FIELDS_CACHE;
        $member_rows_2 = $GLOBALS['FORUM_DB']->query('SELECT f.* FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_member_custom_fields f WHERE ' . str_replace('m.id', 'mf_member_id', $member_or_list), null, null, false, true, array_key_exists('f_member_custom_fields', $TABLE_LANG_FIELDS_CACHE) ? $TABLE_LANG_FIELDS_CACHE['f_member_custom_fields'] : array());
        $member_rows_3 = $GLOBALS['FORUM_DB']->query('SELECT gm_group_id,gm_member_id FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_group_members WHERE gm_validated=1 AND (' . str_replace('m.id', 'gm_member_id', $member_or_list) . ')', null, null, false, true);
        global $MEMBER_CACHE_FIELD_MAPPINGS, $GROUP_MEMBERS_CACHE, $SIGNATURES_CACHE;
        $found_groups = array();
        foreach ($member_rows as $row) {
            $GLOBALS['CNS_DRIVER']->MEMBER_ROWS_CACHED[$row['id']] = $row;

            if (!cns_is_ldap_member($row['id'])) {
                // Primary
                $pg = $GLOBALS['CNS_DRIVER']->get_member_row_field($row['id'], 'm_primary_group');
                $found_groups[$pg] = 1;
                $GROUP_MEMBERS_CACHE[$row['id']][false][false] = array($pg => 1);
            }

            // Signature
            if ((get_page_name() != 'search') && (!is_null($row['m_signature'])) && ($row['m_signature'] !== '') && ($row['m_signature'] !== 0)) {
                $SIGNATURES_CACHE[$row['id']] = get_translated_tempcode('f_members', $row, 'm_signature', $GLOBALS['FORUM_DB']);
            }
        }
        foreach ($member_rows_2 as $row) {
            $MEMBER_CACHE_FIELD_MAPPINGS[$row['mf_member_id']] = $row;
        }
        foreach ($member_rows_3 as $row) {
            if (!cns_is_ldap_member($row['gm_member_id'])) {
                $GROUP_MEMBERS_CACHE[$row['gm_member_id']][false][false][$row['gm_group_id']] = 1;
                $found_groups[$row['gm_group_id']] = 1;
            }
        }

        require_code('cns_groups');
        cns_ensure_groups_cached(array_keys($found_groups));
    }
}

/**
 * Get buttons for showing under a post.
 *
 * @param  array $topic_info Map of topic info.
 * @param  array $_postdetails Map of post info.
 * @param  boolean $may_reply Whether the current member may reply to the topic
 * @param  ID_TEXT $rendering_context Rendering context
 * @return Tempcode The buttons.
 */
function cns_render_post_buttons($topic_info, $_postdetails, $may_reply, $rendering_context = 'cns')
{
    $may_reply_private_post = array_key_exists('may_reply_private_post', $topic_info);

    require_lang('cns');
    require_code('cns_members2');

    $buttons = new Tempcode();

    if ((array_key_exists('may_validate_posts', $topic_info)) && (addon_installed('unvalidated')) && ((($topic_info['validated'] == 0) && ($_postdetails['id'] == $topic_info['first_post_id'])) || ($_postdetails['validated'] == 0))) {
        $map = array('page' => 'topics', 'type' => 'validate_post', 'id' => $_postdetails['id']);
        $test = get_param_string('kfs' . (is_null($topic_info['forum_id']) ? '' : strval($topic_info['forum_id'])), null, true);
        if (($test !== null) && ($test !== '0')) {
            $map['kfs' . (is_null($topic_info['forum_id']) ? '' : strval($topic_info['forum_id']))] = $test;
        }
        $test_threaded = get_param_integer('threaded', null);
        if ($test_threaded !== null) {
            $map['threaded'] = $test_threaded;
        }
        $action_url = build_url($map, get_module_zone('topics'));
        $_title = do_lang_tempcode('VALIDATE_POST');
        $_title_full = new Tempcode();
        $_title_full->attach($_title);
        $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));
        $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => '712fdaee35f378e37b007f3a73246690', 'REL' => 'validate nofollow', 'IMMEDIATE' => true, 'IMG' => 'menu__adminzone__audit__unvalidated', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $action_url)));
    }

    if (($may_reply) && (is_null(get_bot_type()))) {
        $map = array('page' => 'topics', 'type' => 'new_post', 'id' => $_postdetails['topic_id'], 'parent_id' => $_postdetails['id']);
        if ($topic_info['is_threaded'] == 0) {
            $map['quote'] = $_postdetails['id'];
        }
        if (array_key_exists('intended_solely_for', $_postdetails)) {
            $map['intended_solely_for'] = $_postdetails['poster'];
        }
        $test = get_param_string('kfs' . (is_null($topic_info['forum_id']) ? '' : strval($topic_info['forum_id'])), null, true);
        if (($test !== null) && ($test !== '0')) {
            $map['kfs' . (is_null($topic_info['forum_id']) ? '' : strval($topic_info['forum_id']))] = $test;
        }
        $test_threaded = get_param_integer('threaded', null);
        if ($test_threaded !== null) {
            $map['threaded'] = $test_threaded;
        }
        $action_url = build_url($map, get_module_zone('topics'));
        $javascript = null;
        $javascript_explicit_quote = null;

        if ((array_key_exists('message_comcode', $_postdetails)) && (!is_null($_postdetails['message_comcode'])) && (strlen($_postdetails['message_comcode']) < 1024 * 10/*10kb limit, for reasonable performance*/) && (array_key_exists('may_use_quick_reply', $topic_info)) && (!array_key_exists('intended_solely_for', $map))) {
            require_code('comcode_cleanup');
            $replying_to_post = str_replace("\n", '\n', addslashes(comcode_censored_raw_code_access($_postdetails['message_comcode'])));
            $replying_to_post_plain = str_replace("\n", '\n', addslashes(($topic_info['is_threaded'] == 0) ? '' : strip_comcode($_postdetails['message_comcode'])));
            $javascript = 'return topic_reply(' . ($topic_info['is_threaded'] ? 'true' : 'false') . ',this,\'' . strval($_postdetails['id']) . '\',\'' . addslashes($_postdetails['poster_username']) . '\',\'' . $replying_to_post . '\',\'' . $replying_to_post_plain . '\');';
            $javascript_explicit_quote = 'return topic_reply(false,this,\'' . strval($_postdetails['id']) . '\',\'' . addslashes($_postdetails['poster_username']) . '\',\'' . $replying_to_post . '\',\'' . $replying_to_post_plain . '\',true);';
        }
        $_title = do_lang_tempcode(($topic_info['is_threaded'] == 1) ? '_REPLY' : '_QUOTE_POST');
        $_title_full = new Tempcode();
        $_title_full->attach(do_lang_tempcode(($topic_info['is_threaded'] == 1) ? 'REPLY' : 'QUOTE_POST'));
        $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));
        $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => 'fc13d12cfe58324d78befec29a663b4f', 'REL' => 'add reply nofollow', 'IMMEDIATE' => false, 'IMG' => ($topic_info['is_threaded'] == 1) ? 'buttons__new_reply' : 'buttons__new_quote', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $action_url, 'JAVASCRIPT' => $javascript)));

        if ($topic_info['is_threaded'] == 1) { // Second button for replying with explicit quote
            $_title = do_lang_tempcode('QUOTE_POST');
            $_title_full = new Tempcode();
            $_title_full->attach($_title);
            $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));
            $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => 'fc13d12cfe58324d78befec29a663b4f', 'REL' => 'add reply nofollow', 'IMMEDIATE' => false, 'IMG' => 'buttons__new_quote', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $action_url, 'JAVASCRIPT' => $javascript_explicit_quote)));
        }
    }

    if ($rendering_context == 'tickets') {
        if ((array_key_exists('message_comcode', $_postdetails)) && (!is_null($_postdetails['message_comcode']))) {
            $ticket_id = get_param_string('id', null);
            if (!is_null($ticket_id)) {
                require_lang('tickets');
                require_code('tickets');
                $ticket_owner = check_ticket_access($ticket_id);

                if (($ticket_owner == get_member()) || (has_privilege(get_member(), 'support_operator'))) {
                    $_title = do_lang_tempcode('QUOTE_TO_NEW_TICKET');
                    $_title_full = new Tempcode();
                    $_title_full->attach($_title);
                    $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));

                    $action_url = build_url(array('page' => 'tickets', 'type' => 'ticket', 'post_as' => ($ticket_owner == get_member()) ? null : $ticket_owner), get_module_zone('tickets'));

                    $ticket_url = build_url(array('page' => 'tickets', 'type' => 'ticket', 'id' => $ticket_id), get_module_zone('tickets'));
                    $quote_to_new_post = do_lang('POSTING_TICKET_AS', $GLOBALS['FORUM_DRIVER']->get_username(get_member()), $ticket_url->evaluate(), $_postdetails['message_comcode']);
                    $hidden = form_input_hidden('post', $quote_to_new_post);

                    $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => '927d758415a3358d6b69e1587cab1e8d', 'IMMEDIATE' => true, 'HIDDEN' => $hidden, 'IMG' => 'buttons__new_quote', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $action_url, 'TARGET' => '_blank')));
                }
            }
        }
    }

    if ((array_key_exists('may_pt_members', $topic_info)) && ($may_reply_private_post) && ($_postdetails['poster'] != get_member()) && ($_postdetails['poster'] != $GLOBALS['CNS_DRIVER']->get_guest_id()) && (cns_may_whisper($_postdetails['poster'])) && (get_option('overt_whisper_suggestion') == '1')) {
        $whisper_type = (get_option('inline_pp_advertise') == '0') ? 'new_pt' : 'whisper';
        $action_url = build_url(array('page' => 'topics', 'type' => $whisper_type, 'id' => $_postdetails['topic_id'], 'quote' => $_postdetails['id'], 'intended_solely_for' => $_postdetails['poster']), get_module_zone('topics'));
        $_title = do_lang_tempcode('WHISPER');
        $_title_full = new Tempcode();
        $_title_full->attach($_title);
        $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));
        $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => 'fb1c74bae9c553dc160ade85adf289b5', 'REL' => 'add reply contact nofollow', 'IMMEDIATE' => false, 'IMG' => (get_option('inline_pp_advertise') == '0') ? 'buttons__send' : 'buttons__whisper', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $action_url)));
    }

    if (array_key_exists('may_edit', $_postdetails)) {
        $map = array('page' => 'topics', 'type' => 'edit_post', 'id' => $_postdetails['id']);
        if ($rendering_context == 'tickets') {
            $map['redirect'] = static_evaluate_tempcode(build_url(array('page' => 'tickets', 'type' => 'ticket', 'id' => get_param_string('id')), get_module_zone('tickets'), null, false, false, false, '_top'));
        } else {
            $map['redirect'] = get_self_url(true);
        }
        $test = get_param_string('kfs' . (is_null($topic_info['forum_id']) ? '' : strval($topic_info['forum_id'])), null, true);
        if (($test !== null) && ($test !== '0')) {
            $map['kfs' . (is_null($topic_info['forum_id']) ? '' : strval($topic_info['forum_id']))] = $test;
        }
        $test_threaded = get_param_integer('threaded', null);
        if ($test_threaded !== null) {
            $map['threaded'] = $test_threaded;
        }
        $edit_url = build_url($map, get_module_zone('topics'));
        $_title = do_lang_tempcode('EDIT');
        $_title_full = do_lang_tempcode('EDIT_POST');
        $_title_full->attach($_title);
        $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));
        $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => 'f341cfc94b3d705437d43e89f572bff6', 'REL' => 'edit nofollow', 'IMMEDIATE' => false, 'IMG' => 'buttons__edit', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $edit_url)));
    }

    if (array_key_exists('may_delete', $_postdetails)) {
        $map = array('page' => 'topics', 'type' => 'delete_post', 'id' => $_postdetails['id']);
        if ($rendering_context == 'tickets') {
            $map['redirect'] = static_evaluate_tempcode(build_url(array('page' => 'tickets', 'type' => 'ticket', 'id' => get_param_string('id')), get_module_zone('tickets'), null, false, false, false, '_top'));
        } else {
            $map['redirect'] = get_self_url(true);
        }
        $test = get_param_string('kfs' . (is_null($topic_info['forum_id']) ? '' : strval($topic_info['forum_id'])), null, true);
        if (($test !== null) && ($test !== '0')) {
            $map['kfs' . (is_null($topic_info['forum_id']) ? '' : strval($topic_info['forum_id']))] = $test;
        }
        $test_threaded = get_param_integer('threaded', null);
        if ($test_threaded !== null) {
            $map['threaded'] = $test_threaded;
        }
        $delete_url = build_url($map, get_module_zone('topics'));
        $_title = do_lang_tempcode('DELETE');
        $_title_full = new Tempcode();
        $_title_full->attach(do_lang_tempcode('DELETE_POST'));
        $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));
        $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => '8bf6d098ddc217eef75718464dc03d41', 'REL' => 'delete nofollow', 'IMMEDIATE' => false, 'IMG' => 'menu___generic_admin__delete', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $delete_url)));
    }

    if ($rendering_context != 'tickets') {
        if ((array_key_exists('may_report_posts', $topic_info)) && (addon_installed('cns_reported_posts')) && (is_null(get_bot_type()))) {
            $action_url = build_url(array('page' => 'topics', 'type' => 'report_post', 'id' => $_postdetails['id']), get_module_zone('topics'));
            $_title = do_lang_tempcode('_REPORT_POST');
            $_title_full = new Tempcode();
            $_title_full->attach(do_lang_tempcode('REPORT_POST'));
            $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));
            $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => 'f81cbe84f524b4ed9e089c6e89a7c717', 'REL' => 'report nofollow', 'IMMEDIATE' => false, 'IMG' => 'buttons__report', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $action_url, 'JAVASCRIPT' => 'return open_link_as_overlay(this,null,\'100%\');')));
        }

        if ((array_key_exists('may_warn_members', $topic_info)) && ($_postdetails['poster'] != $GLOBALS['CNS_DRIVER']->get_guest_id()) && (addon_installed('cns_warnings'))) {
            $redir_url = get_self_url(true);
            $redir_url .= '#post_' . strval($_postdetails['id']);
            $action_url = build_url(array('page' => 'warnings', 'type' => 'add', 'member_id' => $_postdetails['poster'], 'post_id' => $_postdetails['id'], 'redirect' => $redir_url), get_module_zone('warnings'));
            $_title = do_lang_tempcode('__WARN_MEMBER');
            $_title_full = do_lang_tempcode('WARN_MEMBER');
            $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));
            $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => '2698c51b06a72773ac7135bbfe791318', 'REL' => 'nofollow', 'IMMEDIATE' => false, 'IMG' => 'buttons__warn', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $action_url)));
        }
    }

    if ($_postdetails['has_revisions']) {
        $action_url = build_url(array('page' => 'admin_revisions', 'type' => 'browse', 'resource_types' => 'post', 'resource_id' => $_postdetails['id']), get_module_zone('admin_revisions'));
        $_title = do_lang_tempcode('actionlog:REVISIONS');
        $_title_full = new Tempcode();
        $_title_full->attach($_title);
        $_title_full->attach(do_lang_tempcode('ID_NUM', strval($_postdetails['id'])));
        $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => '6086b2ae226bf2a69d1e34641d22ae21', 'REL' => 'history nofollow', 'IMMEDIATE' => false, 'IMG' => 'buttons__revisions', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $action_url)));
    }

    if ($rendering_context != 'tickets') {
        if ((addon_installed('points')) && (!is_guest()) && (!is_guest($_postdetails['poster'])) && (has_privilege($_postdetails['poster'], 'use_points'))) {
            require_css('points');
            $action_url = build_url(array('page' => 'points', 'type' => 'member', 'id' => $_postdetails['poster']), get_module_zone('points'));
            $_title = do_lang_tempcode('__POINTS_THANKS');
            $_title_full = do_lang_tempcode('POINTS_THANKS');
            $buttons->attach(do_template('BUTTON_SCREEN_ITEM', array('_GUID' => 'a66f98cb4d56bd0d64e9ecc44d357141', 'IMMEDIATE' => false, 'IMG' => 'buttons__points', 'TITLE' => $_title, 'FULL_TITLE' => $_title_full, 'URL' => $action_url)));
        }
    }

    return $buttons;
}

/**
 * Get post emphasis Tempcode.
 *
 * @param  array $_postdetails Map of post info.
 * @return Tempcode The Tempcode.
 */
function cns_get_post_emphasis($_postdetails)
{
    $emphasis = new Tempcode();
    if ($_postdetails['is_emphasised']) {
        $emphasis = do_lang_tempcode('IMPORTANT');
    } elseif (array_key_exists('intended_solely_for', $_postdetails)) {
        $pp_to_displayname = $GLOBALS['FORUM_DRIVER']->get_username($_postdetails['intended_solely_for'], true);
        if (is_null($pp_to_displayname)) {
            $pp_to_displayname = do_lang('UNKNOWN');
        }
        $pp_to_username = $GLOBALS['FORUM_DRIVER']->get_username($_postdetails['intended_solely_for']);
        if (is_null($pp_to_username)) {
            $pp_to_username = do_lang('UNKNOWN');
        }
        $emphasis = do_lang('PP_TO', $pp_to_displayname, $pp_to_username);
    }
    return $emphasis;
}
