<?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    core_feedback_features
 */

/**
 * Standard code module initialisation function.
 *
 * @ignore
 */
function init__feedback()
{
    if (!defined('MAX_LIKES_TO_SHOW')) {
        define('MAX_LIKES_TO_SHOW', 20);
    }

    if (!defined('RATING_TYPE_star_choice')) {
        define('RATING_TYPE_star_choice', 0);
        define('RATING_TYPE_like_dislike', 1);
    }

    global $RATINGS_STRUCTURE;
    $RATINGS_STRUCTURE = array();
    global $REVIEWS_STRUCTURE;
    $REVIEWS_STRUCTURE = array();

    global $RATING_DETAILS_CACHE;
    $RATING_DETAILS_CACHE = array();
}

/**
 * Get the comment topic forum for a feedback scenario, and move an existing comment topic to a different forum if the category has moved and the categories have divergent configured comment topic forums (Conversr only).
 *
 * @param  ID_TEXT $feedback_code The feedback code, which we may have overridden the comment forum against
 * @param  ID_TEXT $id The resource ID whose comment topic may need moving
 * @param  ID_TEXT $category_id The new/current category ID, which we may have overridden the comment forum against
 * @param  ID_TEXT $old_category_id The old category ID, which we may have overridden the comment forum against
 * @return ID_TEXT The comment topic forum
 */
function process_overridden_comment_forum($feedback_code, $id, $category_id, $old_category_id)
{
    $forum_id = find_overridden_comment_forum($feedback_code, $category_id);

    if (($category_id != $old_category_id) && (get_forum_type() == 'cns')) {
        $real_feedback_type = _real_feedback_type($feedback_code);

        // Move if needed
        $old_forum_id = find_overridden_comment_forum($feedback_code, $old_category_id);
        $topic_id = $GLOBALS['FORUM_DRIVER']->find_topic_id_for_topic_identifier($old_forum_id, $real_feedback_type . '_' . $id, do_lang('COMMENT'));
        if (!is_null($topic_id)) {
            $_forum_id = $GLOBALS['FORUM_DRIVER']->forum_id_from_name($forum_id);
            $_old_forum_id = $GLOBALS['FORUM_DRIVER']->forum_id_from_name($old_forum_id);
            require_code('cns_topics_action2');
            cns_move_topics($_old_forum_id, $_forum_id, array($topic_id), false);
        }
    }

    return $forum_id;
}

/**
 * Get the comment topic forum for a feedback scenario.
 *
 * @param  ID_TEXT $feedback_code The feedback code, which we may have overridden the comment forum against
 * @param  ?ID_TEXT $category_id The category ID, which we may have overridden the comment forum against (null: no category ID to override against)
 * @return ID_TEXT The comment topic forum (may be integer as string, or string forum name - so use forum_id_from_name on the result)
 */
function find_overridden_comment_forum($feedback_code, $category_id = null)
{
    if (!is_null($category_id)) {
        $comment_topic_forum = get_value('comment_forum__' . $feedback_code . '__' . $category_id);
        if (is_null($comment_topic_forum)) {
            $comment_topic_forum = get_value('comment_forum__' . $feedback_code);
        }
    } else {
        $comment_topic_forum = get_value('comment_forum__' . $feedback_code);
    }

    if (is_null($comment_topic_forum)) {
        $comment_topic_forum = get_option('comments_forum_name');
    }

    return $comment_topic_forum;
}

/**
 * We allow the feedback type to be over-specified in some places, to allow more configurability - but need to narrow it down in some places.
 *
 * @param  ID_TEXT $content_type Content type
 * @return ID_TEXT Fixed content type
 */
function _real_feedback_type($content_type)
{
    if (substr($content_type, 0, 12) == 'catalogues__') {
        return 'catalogues';
    }
    return $content_type;
}

/**
 * Find who submitted a piece of feedbackable content.
 *
 * @param  ID_TEXT $content_type Content type
 * @param  ID_TEXT $content_id Content ID
 * @return array A tuple: Content title (null means unknown), Submitter (null means unknown), URL (for use within current browser session), URL (for use in emails / sharing), Content meta aware info array
 */
function get_details_behind_feedback_code($content_type, $content_id)
{
    require_code('content');

    $real_feedback_type = _real_feedback_type($content_type);

    $real_content_type = convert_composr_type_codes('feedback_type_code', $real_feedback_type, 'content_type');
    if ($real_content_type != '') {
        require_code('content');
        $cma_ob = get_content_object($real_content_type);
        $info = $cma_ob->info();
        list($content_title, $submitter_id, $cma_info, , $content_url, $content_url_email_safe) = content_get_details($real_content_type, $content_id);
        return array($content_title, $submitter_id, $content_url, $content_url_email_safe, $cma_info);
    }

    return array(null, null, null, null, null);
}

/**
 * Main wrapper function to embed miscellaneous feedback systems into a module output.
 *
 * @param  ID_TEXT $content_type The type (download, etc) that this feedback is for
 * @param  ID_TEXT $content_id Content ID
 * @param  BINARY $allow_rating Whether rating is allowed
 * @param  integer $allow_comments Whether comments/reviews is allowed (reviews allowed=2)
 * @set 0 1 2
 * @param  BINARY $allow_trackbacks Whether trackbacks are allowed
 * @param  BINARY $validated Whether the content is validated
 * @param  ?MEMBER $submitter Content owner (null: none)
 * @param  mixed $content_url URL to view the content
 * @param  SHORT_TEXT $content_title Content title
 * @param  ?string $forum Forum to post comments in (null: site-wide default)
 * @param  ?TIME $time Time of comment topic (null: now)
 * @return array Tuple: Rating details, Comment details, Trackback details
 */
function embed_feedback_systems($content_type, $content_id, $allow_rating, $allow_comments, $allow_trackbacks, $validated, $submitter, $content_url, $content_title, $forum, $time = null)
{
    $real_feedback_type = _real_feedback_type($content_type);

    // Sign up original poster for notifications
    if (get_forum_type() == 'cns') {
        $auto_monitor_contrib_content = $GLOBALS['CNS_DRIVER']->get_member_row_field($submitter, 'm_auto_monitor_contrib_content');
        if ($auto_monitor_contrib_content == 1) {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('notifications_enabled', 'l_setting', array(
                'l_member_id' => $submitter,
                'l_notification_code' => 'comment_posted',
                'l_code_category' => $real_feedback_type . '_' . $content_id,
            ));
            if (is_null($test)) {
                require_code('notifications');
                enable_notifications('comment_posted', $real_feedback_type . '_' . $content_id, $submitter);
            }
        }
    }

    actualise_rating($allow_rating == 1, $content_type, $content_id, $content_url, $content_title);
    if ((!is_null(post_param_string('title', null))) || ($validated == 1)) {
        actualise_post_comment($allow_comments >= 1, $content_type, $content_id, $content_url, $content_title, $forum, false, null, false, false, false, null, null, $time);
    }
    $rating_details = get_rating_box($content_url, $content_title, $content_type, $content_id, $allow_rating == 1, $submitter);
    $comment_details = get_comments($content_type, $allow_comments == 1, $content_id, false, $forum, null, null, false, null, $submitter, $allow_comments == 2);
    $trackback_details = get_trackbacks($content_type, $content_id, $allow_trackbacks == 1);

    if (is_object($content_url)) {
        $content_url = $content_url->evaluate();
    }

    $serialized_options = serialize(array($content_type, $content_id, $allow_comments, $submitter, $content_url, $content_title, $forum, $time));
    require_code('crypt');
    $hash = ratchet_hash($serialized_options, get_site_salt()); // A little security, to ensure $serialized_options is not tampered with

    // AJAX support
    $comment_details->attach(do_template('COMMENT_AJAX_HANDLER', array(
        '_GUID' => 'da533e0f637e4c90ca7ef5a9a23f3203',
        'OPTIONS' => $serialized_options,
        'HASH' => $hash,
        'CONTENT_TYPE' => $content_type,
        'IS_THREADED' => true,
    )));

    return array($rating_details, $comment_details, $trackback_details);
}

/**
 * Do an AJAX comment post
 */
function post_comment_script()
{
    prepare_for_known_ajax_response();

    // Read in context of what we're doing
    if (!isset($_GET['options']) && !isset($_POST['options'])) {
        post_param_string('options'); // Trigger an error
    }
    $options = isset($_POST['options']) ? $_POST['options'] : (isset($_GET['options']) ? $_GET['options'] : '');
    secure_serialized_data($options);
    $_options = @cms_unserialize($options);
    if (!is_array($_options)) {
        warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
    }
    list($content_type, $content_id, $allow_comments, $submitter, $content_url, $content_title, $forum, $time) = $_options;

    // Check security
    $hash = either_param_string('hash');
    require_code('crypt');
    if (ratchet_hash($options, get_site_salt()) != $hash) {
        header('Content-Type: text/plain; charset=' . get_charset());
        exit();
    }

    // Post comment
    actualise_post_comment($allow_comments >= 1, $content_type, $content_id, $content_url, $content_title, $forum, false, null, false, false, false, null, null, $time);

    // Get new comments state
    $comment_details = get_comments($content_type, $allow_comments == 1, $content_id, false, $forum, null, null, false, null, $submitter, $allow_comments == 2);

    // AJAX support
    $comment_details->attach(do_template('COMMENT_AJAX_HANDLER', array(
        '_GUID' => 'da533e0f637e4c90ca7ef5a9a23f3203',
        'OPTIONS' => $options,
        'HASH' => $hash,
        'CONTENT_TYPE' => $content_type,
        'IS_THREADED' => true,
    )));

    // And output as text
    header('Content-Type: text/plain; charset=' . get_charset());
    $comment_details->evaluate_echo();

    exit(); // So auto_append_file cannot run and corrupt our output
}

/**
 * Get Tempcode for doing ratings (sits above get_rating_simple_array)
 *
 * @param  mixed $content_url The URL to where the commenting will pass back to (to put into the comment topic header) (URLPATH or Tempcode)
 * @param  ?string $content_title The title to where the commenting will pass back to (to put into the comment topic header) (null: don't know, but not first post so not important)
 * @param  ID_TEXT $content_type The type (download, etc) that this rating is for
 * @param  ID_TEXT $content_id The ID of the type that this rating is for
 * @param  boolean $allow_rating Whether this resource allows rating (if not, this function does nothing - but it's nice to move out this common logic into the shared function)
 * @param  ?MEMBER $submitter Content owner (null: none)
 * @return Tempcode Tempcode for complete rating box
 */
function get_rating_box($content_url, $content_title, $content_type, $content_id, $allow_rating, $submitter = null)
{
    if ($allow_rating) {
        return display_rating($content_url, $content_title, $content_type, $content_id, 'RATING_BOX', $submitter);
    }

    return new Tempcode();
}

/**
 * Display rating using images
 *
 * @param  mixed $content_url The URL to where the commenting will pass back to (to put into the comment topic header) (URLPATH or Tempcode)
 * @param  ?string $content_title The title to where the commenting will pass back to (to put into the comment topic header) (null: don't know, but not first post so not important)
 * @param  ID_TEXT $content_type The type (download, etc) that this rating is for
 * @param  ID_TEXT $content_id The ID of the type that this rating is for
 * @param  ID_TEXT $display_tpl The template to use to display the rating box
 * @param  ?MEMBER $submitter Content owner (null: none)
 * @return Tempcode Tempcode for complete trackback box
 */
function display_rating($content_url, $content_title, $content_type, $content_id, $display_tpl = 'RATING_INLINE_STATIC', $submitter = null)
{
    $rating_data = get_rating_simple_array($content_url, $content_title, $content_type, $content_id, 'RATING_FORM', $submitter);

    if (is_null($rating_data)) {
        return new Tempcode();
    }

    return do_template($display_tpl, $rating_data);
}

/**
 * Get rating information for the specified resource.
 *
 * @param  mixed $content_url The URL to where the commenting will pass back to (to put into the comment topic header) (URLPATH or Tempcode)
 * @param  ?string $content_title The title to where the commenting will pass back to (to put into the comment topic header) (null: don't know, but not first post so not important)
 * @param  ID_TEXT $content_type The type (download, etc) that this rating is for
 * @param  ID_TEXT $content_id The ID of the type that this rating is for
 * @param  ID_TEXT $form_tpl The template to use to display the rating box
 * @param  ?MEMBER $submitter Content owner (null: none)
 * @return ?array Current rating information (ready to be passed into a template). RATING is the rating (out of 10), NUM_RATINGS is the number of ratings so far, RATING_FORM is the Tempcode of the rating box (null: rating disabled)
 */
function get_rating_simple_array($content_url, $content_title, $content_type, $content_id, $form_tpl = 'RATING_FORM', $submitter = null)
{
    if (get_option('is_on_rating') == '1') {
        $real_feedback_type = _real_feedback_type($content_type);

        global $RATING_DETAILS_CACHE;
        if (isset($RATING_DETAILS_CACHE[$content_type][$content_id][$form_tpl])) {
            return $RATING_DETAILS_CACHE[$content_type][$content_id][$form_tpl];
        }

        $liked_by = mixed();

        // Work out structure first
        global $RATINGS_STRUCTURE;
        $all_rating_criteria = array();
        if (array_key_exists($content_type, $RATINGS_STRUCTURE)) {
            $likes = ($RATINGS_STRUCTURE[$content_type][0] == RATING_TYPE_like_dislike);
            foreach ($RATINGS_STRUCTURE[$content_type][1] as $r => $t) {
                $rating_for_type = $content_type;
                if ($r != '') {
                    $rating_for_type .= '_' . $r;
                }
                $all_rating_criteria[$rating_for_type] = array('TITLE' => $t, 'TYPE' => $r, 'RATING' => '0');
            }
        } else {
            $likes = (get_option('likes') == '1');
            $all_rating_criteria[$content_type] = array('TITLE' => '', 'TYPE' => '', '_NUM_RATINGS' => '0', 'NUM_RATINGS' => '0', 'RATING' => '0');
        }

        // Fill in structure
        $has_ratings = false;
        $overall_num_ratings = 0;
        $overall_rating = 0.0;
        foreach ($all_rating_criteria as $i => $rating_criteria) {
            $rating_for_type = $content_type;
            if ($rating_criteria['TYPE'] != '') {
                $rating_for_type .= '_' . $rating_criteria['TYPE'];
            }

            $_num_ratings = $GLOBALS['SITE_DB']->query_select('rating', array('COUNT(*) AS cnt', 'SUM(rating) AS compound_rating'), array('rating_for_type' => $real_feedback_type, 'rating_for_id' => $content_id), '', 1);
            $num_ratings = $_num_ratings[0]['cnt'];
            if ($num_ratings > 0) {
                $rating = $_num_ratings[0]['compound_rating'];
                $overall_num_ratings = max($overall_num_ratings, $num_ratings);

                if (($num_ratings < MAX_LIKES_TO_SHOW) && ($likes)) { // Show likes
                    if (is_null($liked_by)) {
                        $liked_by = array();
                    }
                    if (count($liked_by) < MAX_LIKES_TO_SHOW) {
                        $_liked_by = $GLOBALS['SITE_DB']->query_select('rating', array('rating_member'), array('rating_for_type' => $real_feedback_type, 'rating_for_id' => $content_id, 'rating' => 10));
                        foreach ($_liked_by as $l) {
                            $username = $GLOBALS['FORUM_DRIVER']->get_username($l['rating_member']);
                            if (!is_null($username)) {
                                $liked_by[] = array(
                                    'MEMBER_ID' => strval($l['rating_member']),
                                    'USERNAME' => $username,
                                );
                                if (count($liked_by) == MAX_LIKES_TO_SHOW) {
                                    break;
                                }
                            }
                        }
                    }
                }

                $calculated_rating = intval(round($rating / floatval($num_ratings)));
                $overall_rating += $calculated_rating;

                $all_rating_criteria[$i] = array('_NUM_RATINGS' => strval($num_ratings), 'NUM_RATINGS' => integer_format($num_ratings), 'RATING' => strval($calculated_rating)) + $all_rating_criteria[$i];

                $extra_metadata = array();
                $extra_metadata['rating' . (($rating_criteria['TYPE'] == '') ? '' : ('_' . $rating_criteria['TYPE']))] = strval($calculated_rating);
                set_extra_request_metadata($extra_metadata);

                $has_ratings = true;
            }
        }

        // Work out possible errors that mighr prevent rating being allowed
        $error = new Tempcode();
        $rate_url = new Tempcode();
        if ((get_option('allow_own_rate') == '0') && ($submitter === get_member()) && (!is_guest())) {
            $error = do_lang_tempcode('RATE_DENIED_OWN');
        } elseif (!has_privilege(get_member(), 'rate', get_page_name())) {
            $error = do_lang_tempcode('RATE_DENIED');
        } elseif (already_rated(array_keys($all_rating_criteria), $content_id)) {
            $error = do_lang_tempcode('NORATE');
        } else {
            static $self_url = null;
            if ($self_url === null) {
                $self_url = get_self_url();
            }
            $rate_url = $self_url;
        }

        // Templating
        $tpl_params = array(
            '_GUID' => 'x28e21cdbc38a3037d083f619bb311af',
            'CONTENT_URL' => $content_url,
            'CONTENT_TITLE' => $content_title,
            'ERROR' => $error,
            'CONTENT_TYPE' => $content_type,
            'ID' => $content_id,
            'URL' => $rate_url,
            'ALL_RATING_CRITERIA' => $all_rating_criteria,
            '_OVERALL_NUM_RATINGS' => strval($overall_num_ratings),
            'OVERALL_NUM_RATINGS' => integer_format($overall_num_ratings),
            'OVERALL_RATING' => strval(intval($overall_rating / floatval(count($all_rating_criteria)))),
            'HAS_RATINGS' => $has_ratings,
            'SIMPLISTIC' => (count($all_rating_criteria) == 1),
            'LIKES' => $likes,
            'LIKED_BY' => $liked_by,
        ) + $all_rating_criteria[$content_type]/*so can assume single rating criteria if want and reference that directly*/;
        $rating_form = do_template($form_tpl, $tpl_params);
        $ret = $tpl_params + array(
            'RATING_FORM' => $rating_form,
        );
        $RATING_DETAILS_CACHE[$content_type][$content_id][$form_tpl] = $ret;
        return $ret;
    }
    return null;
}

/**
 * Find whether you have rated the specified resource before.
 *
 * @param  array $rating_for_types List of types (download, etc) that this rating is for. All need to be rated for it to return true.
 * @param  ID_TEXT $content_id The ID of the type that this rating is for
 * @return boolean Whether the resource has already been rated
 */
function already_rated($rating_for_types, $content_id)
{
    static $not = null;
    if ($not === null) {
        $not = ($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member())) && (get_param_integer('keep_rating_test', 0) == 1);
    }
    if ($not) {
        return false;
    }

    static $cache = array();
    $cache_key = serialize(array($rating_for_types, $content_id));
    if (isset($cache[$cache_key])) {
        return $cache[$cache_key];
    }

    // Main query
    $for_types = '';
    foreach ($rating_for_types as $rating_for_type) {
        $real_feedback_type = _real_feedback_type($rating_for_type);

        if ($for_types != '') {
            $for_types .= ' OR ';
        }
        $for_types .= db_string_equal_to('rating_for_type', $real_feedback_type);
    }
    $query = 'SELECT COUNT(*) FROM ' . get_table_prefix() . 'rating WHERE (' . $for_types . ') AND ' . db_string_equal_to('rating_for_id', $content_id);

    // IP/member vote-once restrictions
    $query .= ' AND (';
    static $ip_restrict = null;
    if ($ip_restrict === null) {
        if ((!$GLOBALS['IS_ACTUALLY_ADMIN']) && ((get_option('vote_member_ip_restrict') == '1') || (is_guest()))) {
            $ip_restrict = db_string_equal_to('rating_ip', get_ip_address());
        } else {
            $ip_restrict = '1=0';
        }
    }
    $query .= $ip_restrict;
    if (!is_guest()) {
        $query .= ' OR ';
        $query .= 'rating_member=' . strval(get_member());
    }
    $query .= ')';

    $times_rated = $GLOBALS['SITE_DB']->query_value_if_there($query, false, true);

    $ret = ($times_rated >= count($rating_for_types));
    $cache[$cache_key] = $ret;
    return $ret;
}

/**
 * Actually adds a rating to the specified resource.
 * It performs full checking of inputs, and will log a hackattack if the rating is not between 1 and 10.
 *
 * @param  boolean $allow_rating Whether this resource allows rating (if not, this function does nothing - but it's nice to move out this common logic into the shared function)
 * @param  ID_TEXT $content_type The type (download, etc) that this rating is for
 * @param  ID_TEXT $content_id The ID of the type that this rating is for
 * @param  mixed $content_url The URL to where the commenting will pass back to (to put into the comment topic header) (URLPATH or Tempcode)
 * @param  ?string $content_title The title to where the commenting will pass back to (to put into the comment topic header) (null: don't know, but not first post so not important)
 */
function actualise_rating($allow_rating, $content_type, $content_id, $content_url, $content_title)
{
    if ((get_option('is_on_rating') == '0') || (!$allow_rating)) {
        return;
    }

    global $RATINGS_STRUCTURE;
    $all_rating_criteria = array();
    if (array_key_exists($content_type, $RATINGS_STRUCTURE)) {
        $all_rating_criteria = array_keys($RATINGS_STRUCTURE[$content_type][1]);
    } else {
        $all_rating_criteria[] = '';
    }

    foreach ($all_rating_criteria as $type) {
        // Has there actually been any rating?
        $rating = post_param_integer('rating__' . $content_type . '__' . $type . '__' . $content_id, null);
        if (is_null($rating)) {
            return;
        }

        actualise_specific_rating($rating, get_page_name(), get_member(), $content_type, $type, $content_id, $content_url, $content_title);
    }

    actualise_give_rating_points();

    // Ok, so just thank 'em
    attach_message(do_lang_tempcode('THANKYOU_FOR_RATING'), 'inform');
}

/**
 * Assign points to the current member for rating.
 */
function actualise_give_rating_points()
{
    if ((!is_guest()) && (addon_installed('points'))) {
        require_code('points');
        $_count = point_info(get_member());
        $count = array_key_exists('points_gained_rating', $_count) ? $_count['points_gained_rating'] : 0;
        $GLOBALS['FORUM_DRIVER']->set_custom_field(get_member(), 'points_gained_rating', $count + 1);
    }
}

/**
 * Implement a rating at the quantum level.
 *
 * @param  ?integer $rating Rating given (null: unrate)
 * @range 1 10
 * @param  ID_TEXT $page_name The page name the rating is on
 * @param  MEMBER $member_id The member doing the rating
 * @param  ID_TEXT $content_type The type (download, etc) that this rating is for
 * @param  ID_TEXT $type The second level type (probably blank)
 * @param  ID_TEXT $content_id The ID of the type that this rating is for
 * @param  mixed $content_url The URL to where the commenting will pass back to (to put into the comment topic header) (URLPATH or Tempcode)
 * @param  ?string $content_title The title to where the commenting will pass back to (to put into the comment topic header) (null: don't know)
 */
function actualise_specific_rating($rating, $page_name, $member_id, $content_type, $type, $content_id, $content_url, $content_title)
{
    if (!is_null($rating)) {
        if (($rating > 10) || ($rating < 1)) {
            log_hack_attack_and_exit('VOTE_CHEAT');
        }
    }

    $real_feedback_type = _real_feedback_type($content_type);

    $rating_for_type = $content_type . (($type == '') ? '' : ('_' . $type));

    if (!has_privilege($member_id, 'rate', $page_name)) {
        return;
    }
    $already_rated = already_rated(array($rating_for_type), $content_id);
    $past_rating = mixed();
    if (!is_null($rating)) {
        if ($already_rated) {
            $past_rating = $GLOBALS['SITE_DB']->query_select_value_if_there('rating', 'rating', array('rating_for_type' => $real_feedback_type, 'rating_for_id' => $content_id, 'rating_member' => $member_id, 'rating_ip' => get_ip_address()));
            // Delete, in preparation for re-rating
            $GLOBALS['SITE_DB']->query_delete('rating', array('rating_for_type' => $real_feedback_type, 'rating_for_id' => $content_id, 'rating_member' => $member_id, 'rating_ip' => get_ip_address()));
        }
    }

    list($_content_title, $submitter, , $safe_content_url, $cma_info) = get_details_behind_feedback_code($content_type, $content_id);
    if (is_null($content_title)) {
        $content_title = $_content_title;
    }
    if ((get_option('allow_own_rate') == '0') && ($member_id === $submitter) && (!is_guest($member_id))) {
        return;
    }

    if (!is_null($rating)) {
        $GLOBALS['SITE_DB']->query_insert('rating', array('rating_for_type' => $real_feedback_type, 'rating_for_id' => $content_id, 'rating_member' => $member_id, 'rating_ip' => get_ip_address(), 'rating_time' => time(), 'rating' => $rating));
    } else {
        $GLOBALS['SITE_DB']->query_delete('rating', array('rating_for_type' => $real_feedback_type, 'rating_for_id' => $content_id, 'rating_member' => $member_id, 'rating_ip' => get_ip_address()));
    }

    // Top rating / liked
    if (($rating === 10) && ($type == '') && ($past_rating !== $rating)) {
        if (!is_null($cma_info)) {
            $content_type_title = do_lang($cma_info['content_type_label']);

            // Special case. Would prefer not to hard-code, but important for usability
            if (($content_type == 'post') && ($content_title == '') && (get_forum_type() == 'cns')) {
                $content_title = do_lang('POST_IN', $GLOBALS['FORUM_DB']->query_select_value('f_topics', 't_cache_first_title', array('id' => $GLOBALS['FORUM_DB']->query_select_value('f_posts', 'p_topic_id', array('id' => intval($content_id))))));
            }

            $real_content_type = convert_composr_type_codes('feedback_type_code', $real_feedback_type, 'content_type');

            if ((!is_null($submitter)) && (!is_guest($submitter))) {
                // Give points
                if ($member_id != $submitter) {
                    if ((addon_installed('points')) && (!$already_rated)) {
                        require_code('points2');
                        require_lang('points');
                        system_gift_transfer(do_lang('CONTENT_LIKED'), intval(get_option('points_if_liked')), $submitter);
                    }
                }

                // Notification
                require_code('notifications');
                $displayname = $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true);
                $username = $GLOBALS['FORUM_DRIVER']->get_username(get_member());
                $subject = do_lang('CONTENT_LIKED_NOTIFICATION_MAIL_SUBJECT', get_site_name(), ($content_title == '') ? cms_mb_strtolower($content_type_title) : $content_title, array($displayname, $username));
                $rendered = '';
                if ($real_content_type != '') {
                    require_code('content');
                    $cma_ob = get_content_object($real_content_type);
                    $cma_content_row = content_get_row($content_id, $cma_ob->info());
                    if (!is_null($cma_content_row)) {
                        push_no_keep_context();
                        $rendered = static_evaluate_tempcode($cma_ob->run($cma_content_row, '_SEARCH', $real_content_type != 'post'/*FUDGE - to conserve space*/, true));
                        pop_no_keep_context();
                    }
                }
                $mail = do_notification_lang('CONTENT_LIKED_NOTIFICATION_MAIL', comcode_escape(get_site_name()), comcode_escape(($content_title == '') ? cms_mb_strtolower($content_type_title) : $content_title), array(comcode_escape(is_object($safe_content_url) ? $safe_content_url->evaluate() : $safe_content_url), $rendered, comcode_escape($displayname), comcode_escape($username)));
                dispatch_notification('like', null, $subject, $mail, array($submitter), A_FROM_SYSTEM_PRIVILEGED);
            }

            $privacy_ok = true;
            if ((addon_installed('content_privacy')) && ($real_content_type != '')) {
                require_code('content_privacy');
                $privacy_ok = has_privacy_access($real_content_type, $content_id, $GLOBALS['FORUM_DRIVER']->get_guest_id());
            }
            if ($privacy_ok) {
                // Put on activity wall / whatever
                require_code('users2');
                if (may_view_content_behind(get_modal_user(), $real_content_type, $content_id, 'feedback_type_code')) {
                    if (is_null($submitter)) {
                        $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                    }

                    $activity_type = ((is_null($submitter)) || (is_guest($submitter))) ? '_ACTIVITY_LIKES' : 'ACTIVITY_LIKES';
                    $_safe_content_url = is_object($safe_content_url) ? $safe_content_url->evaluate() : $safe_content_url;
                    if ($_safe_content_url == '') {
                        $_safe_content_url = is_object($content_url) ? $content_url->evaluate() : $content_url;
                    }
                    $content_page_link = url_to_page_link($_safe_content_url);
                    require_code('activities');
                    if ($content_title == '') {
                        syndicate_described_activity($activity_type . '_UNTITLED', cms_mb_strtolower($content_type_title), $content_type_title, '', $content_page_link, '', '', convert_composr_type_codes('feedback_type_code', $real_feedback_type, 'addon_name'), 1, null, false, $submitter);
                    } else {
                        if ($content_type_title == $real_content_type) {
                            $activity_type .= '_UNTYPED';
                        }
                        syndicate_described_activity($activity_type, $content_title, cms_mb_strtolower($content_type_title), $content_type_title, $content_page_link, '', '', convert_composr_type_codes('feedback_type_code', $real_feedback_type, 'addon_name'), 1, null, false, $submitter);
                    }
                }
            }
        }
    }

    // Enter them for a prize draw to win a free jet
    // NOT IMPLEMENTED- Anyone want to donate the jet?
}

/**
 * Get the Tempcode containing all the comments posted, and the comments posting form for the specified resource.
 *
 * @param  ID_TEXT $content_type The type (download, etc) that this commenting is for
 * @param  boolean $allow_comments Whether this resource allows comments (if not, this function does nothing - but it's nice to move out this common logic into the shared function)
 * @param  ID_TEXT $content_id The ID of the type that this commenting is for
 * @param  boolean $invisible_if_no_comments Whether the comment box will be invisible if there are not yet any comments (and you're not staff)
 * @param  ?string $forum The name of the forum to use (null: default comment forum)
 * @param  ?string $post_warning The default post to use (null: standard courtesy warning)
 * @param  ?mixed $_comments The raw comment array (null: lookup). This is useful if we want to pass it through a filter
 * @param  boolean $explicit_allow Whether to skip permission checks
 * @param  ?boolean $reverse Whether to show in reverse date order (affects default search order only) (null: read config)
 * @param  ?MEMBER $highlight_by_user User to highlight the posts of (null: none)
 * @param  boolean $allow_reviews Whether to allow ratings along with the comment (like reviews)
 * @param  ?integer $num_to_show_limit Maximum to load (null: default)
 * @return Tempcode The Tempcode for the comment topic
 */
function get_comments($content_type, $allow_comments, $content_id, $invisible_if_no_comments = false, $forum = null, $post_warning = null, $_comments = null, $explicit_allow = false, $reverse = null, $highlight_by_user = null, $allow_reviews = false, $num_to_show_limit = null)
{
    if (((get_option('is_on_comments') == '1') && (get_forum_type() != 'none') && ((get_forum_type() != 'cns') || (addon_installed('cns_forum'))) && (($allow_reviews) || ($allow_comments))) || ($explicit_allow)) {
        $real_feedback_type = _real_feedback_type($content_type);

        if (is_null($forum)) {
            $forum = get_option('comments_forum_name');
        }

        require_code('topics');
        $renderer = new CMS_Topic();

        return $renderer->render_as_comment_topic($real_feedback_type, $content_id, $allow_comments, $invisible_if_no_comments, $forum, $post_warning, $_comments, $explicit_allow, $reverse, $highlight_by_user, $allow_reviews, $num_to_show_limit);
    }

    return new Tempcode(); // No franchise to render comments
}

/**
 * Topic titles/descriptions (depending on forum driver) are encoded for both human readable data, and a special ID code: this will extract just the ID code, or return the whole thing if no specific pattern match
 *
 * @param  string $full_text Potentially complex topic title
 * @return string Simplified topic title
 */
function extract_topic_identifier($full_text)
{
    $matches = array();
    if (preg_match('#: \#(.*)$#', $full_text, $matches) != 0) {
        return $matches[1];
    }
    return $full_text;
}

/**
 * Add comments to the specified resource.
 *
 * @param  boolean $allow_comments Whether this resource allows comments (if not, this function does nothing - but it's nice to move out this common logic into the shared function)
 * @param  ID_TEXT $content_type The type (download, etc) that this commenting is for
 * @param  ID_TEXT $content_id The ID of the type that this commenting is for
 * @param  mixed $content_url The URL to where the commenting will pass back to (to put into the comment topic header) (URLPATH or Tempcode)
 * @param  ?string $content_title The title to where the commenting will pass back to (to put into the comment topic header) (null: don't know, but not first post so not important)
 * @param  ?string $forum The name of the forum to use (null: default comment forum)
 * @param  boolean $avoid_captcha Whether to not require a captcha
 * @param  ?BINARY $validated Whether the post is validated (null: unknown, find whether it needs to be marked unvalidated initially). This only works with the Conversr driver (hence is the last parameter).
 * @param  boolean $explicit_allow Whether to force allowance
 * @param  boolean $no_success_message Whether to skip a success message
 * @param  boolean $private Whether posts made should not be shared
 * @param  ?string $post_title Title of the post (null: lookup from POST environment)
 * @param  ?string $post Body of the post (null: lookup from POST environment)
 * @param  ?TIME $time Time of comment topic (null: now)
 * @return boolean Whether a hidden post has been made
 */
function actualise_post_comment($allow_comments, $content_type, $content_id, $content_url, $content_title, $forum = null, $avoid_captcha = false, $validated = null, $explicit_allow = false, $no_success_message = false, $private = false, $post_title = null, $post = null, $time = null)
{
    if (!$explicit_allow) {
        if ((get_option('is_on_comments') == '0') || (!$allow_comments)) {
            return false;
        }

        if (!has_privilege(get_member(), 'comment', get_page_name())) {
            return false;
        }
    }

    if (running_script('preview')) {
        return false;
    }

    $real_feedback_type = _real_feedback_type($content_type);

    $forum_tie = (get_option('is_on_strong_forum_tie') == '1');

    if (addon_installed('captcha')) {
        if (((array_key_exists('post', $_POST)) && ($_POST['post'] != '')) && (!$avoid_captcha)) {
            require_code('captcha');
            enforce_captcha();
        }
    }

    if (is_null($post_title)) {
        $post_title = post_param_string('title', null);
    }
    if ((is_null($post_title)) && (!$forum_tie)) {
        return false;
    }

    if (is_null($post)) {
        $post = post_param_string('post', null);
        if (($post == do_lang('POST_WARNING')) || ($post == do_lang('THREADED_REPLY_NOTICE', do_lang('POST_WARNING')))) {
            $post = '';
        }
    }
    if (!is_null($post_title)) {
        if (($post == '') && ($post_title !== '')) {
            if (($post == '') && ($post_title != '')) {
                $post = $post_title;
                $post_title = '';
            }
            if ($post == '') {
                warn_exit(do_lang_tempcode('NO_PARAMETER_SENT', 'post'));
            }
        }
    }

    $email = trim(post_param_string('email', ''));
    if ($email != '') {
        $body = '> ' . str_replace("\n", "\n" . '> ', $post);
        if (substr($body, -2) == '> ') {
            $body = substr($body, 0, strlen($body) - 2);
        }
        if (get_page_name() != 'tickets') {
            $post .= '{+START,IF,{$IS_ADMIN}}';
        }
        $post .= "\n\n" . '[email subject="Re: ' . comcode_escape($post_title) . ' [' . get_site_name() . ']" body="' . comcode_escape($body) . '"]' . $email . '[/email]' . "\n\n";
        if (get_page_name() != 'tickets') {
            $post .= '{+END}';
        }
    }

    $content_title = strip_comcode($content_title);

    if (is_null($forum)) {
        $forum = get_option('comments_forum_name');
    }

    $content_url_flat = (is_object($content_url) ? $content_url->evaluate() : $content_url);

    $_parent_id = post_param_string('parent_id', '');
    $parent_id = ($_parent_id == '') ? null : intval($_parent_id);

    list(, $submitter, , $safe_content_url, $cma_info) = get_details_behind_feedback_code($content_type, $content_id);

    if (get_forum_type() == 'cns') {
        require_code('cns_posts_action2');
        $poster_name_if_guest = cns_get_safe_specified_poster_name(false);
    } else {
        $poster_name_if_guest = post_param_string('poster_name_if_guest', '');
    }
    list($topic_id, $is_hidden) = $GLOBALS['FORUM_DRIVER']->make_post_forum_topic(
        // Define scope
        $forum,
        $real_feedback_type . '_' . $content_id,

        // What is being posted
        get_member(),
        $post_title,
        $post,

        // Define more about scope
        $content_title,
        do_lang('COMMENT'),
        $content_url_flat,

        // Define more about what is being posted,
        $time,
        null,
        $validated,
        $explicit_allow ? 1 : null,
        $explicit_allow,
        $poster_name_if_guest,
        $parent_id,
        false,

        // Do not send notifications to someone also getting one defined by the following
        ((!$private) && ($post != '')) ? 'comment_posted' : null,
        ((!$private) && ($post != '')) ? ($real_feedback_type . '_' . $content_id) : null,

        null, // current time
        $submitter
    );

    if (!is_null($topic_id)) {
        if (!is_integer($forum)) {
            $forum_id = $GLOBALS['FORUM_DRIVER']->forum_id_from_name($forum);
        } else {
            $forum_id = intval($forum);
        }

        if ((get_forum_type() == 'cns') && (!is_null($GLOBALS['LAST_POST_ID']))) {
            $extra_review_ratings = array();
            global $REVIEWS_STRUCTURE;
            if (array_key_exists($content_type, $REVIEWS_STRUCTURE)) {
                $reviews_rating_criteria = $REVIEWS_STRUCTURE[$content_type];
            } else {
                $reviews_rating_criteria[] = '';
            }

            foreach ($reviews_rating_criteria as $rating_type) {
                // Has there actually been any rating?
                $rating = post_param_integer('review_rating__' . fix_id($rating_type), null);

                if (!is_null($rating)) {
                    if (($rating > 10) || ($rating < 1)) {
                        log_hack_attack_and_exit('VOTE_CHEAT');
                    }

                    $GLOBALS['SITE_DB']->query_insert('review_supplement', array(
                        'r_topic_id' => $GLOBALS['LAST_TOPIC_ID'],
                        'r_post_id' => $GLOBALS['LAST_POST_ID'],
                        'r_rating_type' => $rating_type,
                        'r_rating_for_type' => $real_feedback_type,
                        'r_rating_for_id' => $content_id,
                        'r_rating' => $rating,
                    ));
                }
            }
        }
    }

    if ((!$private) && ($post != '')) {
        $real_content_type = convert_composr_type_codes('feedback_type_code', $real_feedback_type, 'content_type');

        $content_type_title = $real_content_type;
        if (!is_null($cma_info)) {
            $content_type_title = do_lang($cma_info['content_type_label']);
        }

        // Notification
        require_code('notifications');
        $displayname = $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true);
        $username = $GLOBALS['FORUM_DRIVER']->get_username(get_member());
        $subject = do_lang('NEW_COMMENT_SUBJECT', get_site_name(), ($content_title == '') ? cms_mb_strtolower($content_type_title) : $content_title, array($post_title, $displayname, $username), get_site_default_lang());
        $username = $GLOBALS['FORUM_DRIVER']->get_username(get_member());
        $message_raw = do_notification_lang('NEW_COMMENT_BODY', comcode_escape(get_site_name()), comcode_escape(($content_title == '') ? cms_mb_strtolower($content_type_title) : $content_title), array(($post_title == '') ? do_lang('NO_SUBJECT') : $post_title, post_param_string('post'), comcode_escape($content_url_flat), comcode_escape($displayname), strval(get_member()), comcode_escape($username)), get_site_default_lang());
        if (addon_installed('content_privacy')) {
            require_code('content_privacy');
            $privacy_limits = privacy_limits_for($real_content_type, $content_id);
        } else {
            $privacy_limits = null;
        }
        dispatch_notification('comment_posted', $real_feedback_type . '_' . $content_id, $subject, $message_raw, $privacy_limits);

        // Is the user gonna automatically enable notifications for this?
        if (get_forum_type() == 'cns') {
            $auto_monitor_contrib_content = $GLOBALS['CNS_DRIVER']->get_member_row_field(get_member(), 'm_auto_monitor_contrib_content');
            if ($auto_monitor_contrib_content == 1) {
                enable_notifications('comment_posted', $real_feedback_type . '_' . $content_id);
            }
        }

        $privacy_ok = true;
        if ((addon_installed('content_privacy')) && ($real_content_type != '')) {
            require_code('content_privacy');
            $privacy_ok = has_privacy_access($real_content_type, $content_id, $GLOBALS['FORUM_DRIVER']->get_guest_id());
        }
        if ($privacy_ok) {
            // Activity
            require_code('users2');
            if (may_view_content_behind(get_modal_user(), $real_content_type, $content_id, 'feedback_type_code')) {
                if (is_null($submitter)) {
                    $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                $activity_type = ((is_null($submitter)) || (is_guest($submitter))) ? '_ADDED_COMMENT_ON' : 'ADDED_COMMENT_ON';
                $_safe_content_url = is_object($safe_content_url) ? $safe_content_url->evaluate() : $safe_content_url;
                if ($_safe_content_url == '') {
                    $_safe_content_url = is_object($content_url) ? $content_url->evaluate() : $content_url;
                }
                $content_page_link = url_to_page_link($_safe_content_url);
                require_code('activities');
                if ($content_title == '') {
                    syndicate_described_activity($activity_type . '_UNTITLED', cms_mb_strtolower($content_type_title), $content_type_title, '', $content_page_link, '', '', convert_composr_type_codes('feedback_type_code', $real_feedback_type, 'addon_name'), 1, null, false, $submitter);
                } else {
                    if ($content_type_title == $real_content_type) {
                        $activity_type .= '_UNTYPED';
                    }
                    syndicate_described_activity($activity_type, $content_title, cms_mb_strtolower($content_type_title), $content_type_title, $content_page_link, '', '', convert_composr_type_codes('feedback_type_code', $real_feedback_type, 'addon_name'), 1, null, false, $submitter);
                }
            }
        }
    }

    if (($post != '') && ($forum_tie) && (!$no_success_message)) {
        require_code('site2');
        assign_refresh($GLOBALS['FORUM_DRIVER']->topic_url($GLOBALS['FORUM_DRIVER']->find_topic_id_for_topic_identifier($forum, $real_feedback_type . '_' . $content_id, do_lang('COMMENT')), $forum, true), 0.0);
    }

    if (($post != '') && (!$no_success_message)) {
        attach_message(do_lang_tempcode('SUCCESS'));
    }

    return $is_hidden;
}

/**
 * Update the spacer post of a comment topic, after an edit.
 *
 * @param  boolean $allow_comments Whether this resource allows comments (if not, this function does nothing - but it's nice to move out this common logic into the shared function)
 * @param  ID_TEXT $content_type The type (download, etc) that this commenting is for
 * @param  ID_TEXT $content_id The ID of the type that this commenting is for
 * @param  mixed $content_url The URL to where the commenting will pass back to (to put into the comment topic header) (URLPATH or Tempcode)
 * @param  ?string $content_title The title to where the commenting will pass back to (to put into the comment topic header) (null: don't know, but not first post so not important)
 * @param  ?string $forum The name of the forum to use (null: default comment forum)
 * @param  ?AUTO_LINK $post_id ID of spacer post (null: unknown)
 */
function update_spacer_post($allow_comments, $content_type, $content_id, $content_url, $content_title, $forum = null, $post_id = null)
{
    if ((get_option('is_on_comments') == '0') || (!$allow_comments)) {
        return;
    }
    if (get_forum_type() != 'cns') {
        return;
    }

    $real_feedback_type = _real_feedback_type($content_type);

    $home_link = is_null($content_title) ? new Tempcode() : hyperlink($content_url, $content_title, false, true);

    if (is_null($forum)) {
        $forum = get_option('comments_forum_name');
    }
    if (!is_integer($forum)) {
        $forum_id = $GLOBALS['FORUM_DRIVER']->forum_id_from_name($forum);
        if (is_null($forum_id)) {
            return;
        }
    } else {
        $forum_id = intval($forum);
    }

    $content_title = strip_comcode($content_title);

    foreach (($post_id !== null) ? array(get_site_default_lang()) : array_keys(find_all_langs()) as $lang) {
        if (is_null($post_id)) {
            $topic_id = $GLOBALS['FORUM_DRIVER']->find_topic_id_for_topic_identifier(strval($forum_id), $real_feedback_type . '_' . $content_id, do_lang('COMMENT'));
            if (is_null($topic_id)) {
                continue;
            }
            $post_id = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_posts', 'MIN(id)', array('p_topic_id' => $topic_id));
            if (is_null($post_id)) {
                continue;
            }
        } else {
            $topic_id = $GLOBALS['FORUM_DB']->query_select_value('f_posts', 'p_topic_id', array('id' => $post_id));
        }

        $spacer_title = is_null($content_title) ? ($real_feedback_type . '_' . $content_id) : ($content_title . ' (#' . $real_feedback_type . '_' . $content_id . ')');
        $spacer_post = '[semihtml]' . do_lang('SPACER_POST', $home_link->evaluate(), '', '', $lang) . '[/semihtml]';

        if (get_forum_type() == 'cns') {
            require_code('cns_posts_action3');
            cns_edit_post($post_id, 1, is_null($content_title) ? $spacer_title : $content_title, $spacer_post, 0, 0, null, false, false, '', false);
            require_code('cns_topics_action2');
            cns_edit_topic($topic_id, do_lang('COMMENT', null, null, null, $lang) . ': #' . $real_feedback_type . '_' . $content_id, null, null, null, null, null, null, '', null, $home_link->evaluate(), false);
        }
    }
}

/**
 * Get the Tempcode containing all the trackbacks received, and the trackback posting form for the specified resource.
 *
 * @param  ID_TEXT $content_type The type (download, etc) that this trackback is for
 * @param  ID_TEXT $content_id The ID of the type that this trackback is for
 * @param  boolean $allow_trackback Whether this resource allows trackback (if not, this function does nothing - but it's nice to move out this common logic into the shared function)
 * @param  ID_TEXT $type The type of details being fetched (currently: blank or XML)
 * @return Tempcode Tempcode for complete trackback box
 */
function get_trackbacks($content_type, $content_id, $allow_trackback, $type = '')
{
    if (($type != '') && ($type != 'xml')) {
        $type = '';
    }

    if ((get_option('is_on_trackbacks') == '1') && ($allow_trackback)) {
        require_lang('trackbacks');

        $real_feedback_type = _real_feedback_type($content_type);

        $trackbacks = $GLOBALS['SITE_DB']->query_select('trackbacks', array('*'), array('trackback_for_type' => $real_feedback_type, 'trackback_for_id' => $content_id), 'ORDER BY trackback_time DESC', intval(get_option('general_safety_listing_limit')));

        $content = new Tempcode();
        $items = new Tempcode();

        global $CURRENT_SCREEN_TITLE;

        if (is_null($CURRENT_SCREEN_TITLE)) {
            $CURRENT_SCREEN_TITLE = '';
        }

        foreach ($trackbacks as $value) {
            if ($type == '') {
                $trackback_rendered = do_template('TRACKBACK', array(
                    '_GUID' => '128e21cdbc38a3037d083f619bb311ae',
                    'ID' => strval($value['id']),
                    'TIME_RAW' => strval($value['trackback_time']),
                    'TIME' => get_timezoned_date($value['trackback_time']),
                    'URL' => $value['trackback_url'],
                    'TITLE' => $value['trackback_title'],
                    'EXCERPT' => $value['trackback_excerpt'],
                    'NAME' => $value['trackback_name'],
                ));
                $content->attach($trackback_rendered);
            } else {
                $trackback_rendered_xml = do_template('TRACKBACK_XML', array(
                    '_GUID' => 'a3fa8ab9f0e58bf2ad88b0980c186245',
                    'TITLE' => $value['trackback_title'],
                    'LINK' => $value['trackback_url'],
                    'EXCERPT' => $value['trackback_excerpt'],
                ));
                $items->attach($trackback_rendered_xml);
            }
        }

        if ((count($trackbacks) < 1) && ($type == 'xml')) {
            $trackback_xml_error = do_template('TRACKBACK_XML_ERROR', array(
                '_GUID' => '945e2fcb510816caf323ba3704209430',
                'TRACKBACK_ERROR' => do_lang_tempcode('NO_TRACKBACKS'),
            ), null, false, null, '.xml', 'xml');
            $content->attach($trackback_xml_error);
        }

        if ($type == '') {
            $output = do_template('TRACKBACK_WRAPPER', array(
                '_GUID' => '1bc2c42a54fdf4b0a10e8e1ea45f6e4f',
                'TRACKBACKS' => $content,
                'TRACKBACK_FEEDBACK_TYPE' => $real_feedback_type,
                'TRACKBACK_ID' => $content_id,
                'TRACKBACK_TITLE' => $CURRENT_SCREEN_TITLE,
            ));
        } else {
            $trackback_xml = do_template('TRACKBACK_XML_LISTING', array(
                '_GUID' => '3bff402f15395f4648a2b5af33de8285',
                'ITEMS' => $items,
                'LINK_PAGE' => $real_feedback_type,
                'LINK_ID' => $content_id,
            ), null, false, null, '.xml', 'xml');
            $content->attach($trackback_xml);
            $output = $content;
        }
    } else {
        $output = new Tempcode();
    }

    return $output;
}

/**
 * Add trackbacks to the specified resource.
 *
 * @param  boolean $allow_trackbacks Whether this resource allows trackback (if not, this function does nothing - but it's nice to move out this common logic into the shared function)
 * @param  ID_TEXT $content_type The type (download, etc) that this trackback is for
 * @param  ID_TEXT $content_id The ID of the type that this trackback is for
 * @return boolean Whether trackbacks are on
 */
function actualise_post_trackback($allow_trackbacks, $content_type, $content_id)
{
    if ((get_option('is_on_trackbacks') == '0') || (!$allow_trackbacks)) {
        return false;
    }

    require_code('antispam');
    inject_action_spamcheck();

    $url = post_param_string('url', null);
    if (is_null($url)) {
        return false;
    }
    $title = post_param_string('title', $url);
    $excerpt = post_param_string('excerpt', '');
    $name = post_param_string('blog_name', $url);

    $real_feedback_type = _real_feedback_type($content_type);

    $GLOBALS['SITE_DB']->query_insert('trackbacks', array('trackback_for_type' => $real_feedback_type, 'trackback_for_id' => $content_id, 'trackback_ip' => get_ip_address(), 'trackback_time' => time(), 'trackback_url' => $url, 'trackback_title' => $title, 'trackback_excerpt' => $excerpt, 'trackback_name' => $name));

    return true;
}
