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

/**
 * Module page class.
 */
class Module_tickets
{
    /**
     * Find details of the module.
     *
     * @return ?array Map of module info (null: module is disabled).
     */
    public function info()
    {
        $info = array();
        $info['author'] = 'Chris Graham';
        $info['organisation'] = 'ocProducts';
        $info['hacked_by'] = null;
        $info['hack_version'] = null;
        $info['version'] = 6;
        $info['update_require_upgrade'] = true;
        $info['locked'] = false;
        return $info;
    }

    /**
     * Uninstall the module.
     */
    public function uninstall()
    {
        $GLOBALS['SITE_DB']->drop_table_if_exists('ticket_types');
        $GLOBALS['SITE_DB']->drop_table_if_exists('tickets');
        $GLOBALS['SITE_DB']->drop_table_if_exists('ticket_known_emailers');
        $GLOBALS['SITE_DB']->drop_table_if_exists('ticket_extra_access');

        delete_privilege('view_others_tickets');
        delete_privilege('support_operator');

        $GLOBALS['SITE_DB']->query_delete('group_category_access', array('module_the_name' => 'tickets'));

        if (get_forum_type() == 'cns') {
            require_lang('tickets');
            $forum_id = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_forums', 'id', array('f_parent_forum' => db_get_first_id(), 'f_name' => do_lang('TICKET_FORUM_NAME', null, null, null, get_site_default_lang())));
            if ($forum_id !== null) {
                require_code('cns_forums_action');
                require_code('cns_forums_action2');
                cns_delete_forum($forum_id);
            }
        }
    }

    /**
     * Install the module.
     *
     * @param  ?integer $upgrade_from What version we're upgrading from (null: new install)
     * @param  ?integer $upgrade_from_hack What hack version we're upgrading from (null: new-install/not-upgrading-from-a-hacked-version)
     */
    public function install($upgrade_from = null, $upgrade_from_hack = null)
    {
        require_lang('tickets');

        if ((is_null($upgrade_from)) || ($upgrade_from < 6)) {
            $GLOBALS['SITE_DB']->create_table('ticket_known_emailers', array(
                'email_address' => '*SHORT_TEXT',
                'member_id' => 'MEMBER',
            ), false, false, true);

            $GLOBALS['SITE_DB']->create_table('ticket_extra_access', array(
                'ticket_id' => '*SHORT_TEXT',
                'member_id' => '*MEMBER',
            ), false, false, true);
        }

        if ((!is_null($upgrade_from)) && ($upgrade_from < 6)) {
            $GLOBALS['SITE_DB']->delete_index_if_exists('ticket_types', '#ticket_type');
            $GLOBALS['SITE_DB']->alter_table_field('ticket_types', 'ticket_type', '*AUTO', 'id');
            $GLOBALS['SITE_DB']->add_table_field('ticket_types', 'ticket_type_name', 'SHORT_TRANS', 0);
            $GLOBALS['SITE_DB']->query('UPDATE ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'ticket_types SET ticket_type_name=id');

            $ticket_types = $GLOBALS['SITE_DB']->query_select('ticket_types', array('id', 'ticket_type_name'));
            foreach ($ticket_types as $ticket_type) {
                $GLOBALS['SITE_DB']->query_update('group_category_access', array('category_name' => strval($ticket_type['id'])), array('category_name' => get_translated_text($ticket_type['ticket_type_name']), 'module_the_name' => 'tickets'));
            }

            if (get_forum_type() == 'cns') {
                require_code('tickets');
                $forum_id = get_ticket_forum_id(null, null, false, true);
                if ($forum_id !== null) {
                    $GLOBALS['FORUM_DB']->query('UPDATE ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_topics SET t_description=' . db_function('REPLACE', array('t_description', '\'Support ticket:\'', '\'Ticket:\'')) . ' WHERE t_forum_id=' . strval($forum_id));
                }
            }
        }

        if ((!is_null($upgrade_from)) && ($upgrade_from < 5)) {
            $GLOBALS['SITE_DB']->delete_table_field('ticket_types', 'send_sms_to');
        }

        if (is_null($upgrade_from)) {
            $GLOBALS['SITE_DB']->create_table('tickets', array(
                'ticket_id' => '*SHORT_TEXT',
                'topic_id' => 'AUTO_LINK',
                'forum_id' => 'AUTO_LINK',
                'ticket_type' => 'AUTO_LINK',
            ), false, false, true);

            $GLOBALS['SITE_DB']->create_table('ticket_types', array(
                'id' => '*AUTO',
                'ticket_type_name' => 'SHORT_TRANS',
                'guest_emails_mandatory' => 'BINARY',
                'search_faq' => 'BINARY',
                'cache_lead_time' => '?TIME',
            ));

            $default_types = array(
                'TT_OTHER',
                'TT_COMPLAINT',
            );
            foreach ($default_types as $ticket_type_name) {
                $map = array(
                    'guest_emails_mandatory' => 0,
                    'search_faq' => 0,
                    'cache_lead_time' => null,
                );
                $map += insert_lang('ticket_type_name', do_lang($ticket_type_name), 1);
                $ticket_type_id = $GLOBALS['SITE_DB']->query_insert('ticket_types', $map, true);

                require_code('permissions2');
                set_global_category_access('tickets', $ticket_type_id);
            }

            add_privilege('SUPPORT_TICKETS', 'view_others_tickets', false);
            add_privilege('SUPPORT_TICKETS', 'support_operator', false);

            if (get_forum_type() == 'cns') {
                $moderator_groups = $GLOBALS['FORUM_DRIVER']->get_moderator_groups();
                $staff_access = array();
                foreach ($moderator_groups as $id) {
                    $staff_access[$id] = 5;
                }
                cns_require_all_forum_stuff();
                require_code('cns_forums_action');
                require_code('cns_forums_action2');
                $GLOBALS['CNS_DRIVER'] = $GLOBALS['FORUM_DRIVER'];
                cns_make_forum(do_lang('TICKET_FORUM_NAME', null, null, null, get_site_default_lang()), '', db_get_first_id() + 1, $staff_access, db_get_first_id());
            }
        }
    }

    /**
     * Find entry-points available within this module.
     *
     * @param  boolean $check_perms Whether to check permissions.
     * @param  ?MEMBER $member_id The member to check permissions as (null: current user).
     * @param  boolean $support_crosslinks Whether to allow cross links to other modules (identifiable via a full-page-link rather than a screen-name).
     * @param  boolean $be_deferential Whether to avoid any entry-point (or even return null to disable the page in the Sitemap) if we know another module, or page_group, is going to link to that entry-point. Note that "!" and "browse" entry points are automatically merged with container page nodes (likely called by page-groupings) as appropriate.
     * @return ?array A map of entry points (screen-name=>language-code/string or screen-name=>[language-code/string, icon-theme-image]) (null: disabled).
     */
    public function get_entry_points($check_perms = true, $member_id = null, $support_crosslinks = true, $be_deferential = false)
    {
        return array(
            'browse' => array('SUPPORT_TICKETS', 'menu/site_meta/tickets'),
        );
    }

    public $title;
    public $ticket_type_id;

    /**
     * Module pre-run function. Allows us to know metadata for <head> before we start streaming output.
     *
     * @return ?Tempcode Tempcode indicating some kind of exceptional output (null: none).
     */
    public function pre_run()
    {
        $type = get_param_string('type', 'browse');

        require_lang('tickets');

        set_feed_url('?mode=tickets&select=');

        if ($type == 'browse') {
            if (!is_guest()) {
                // Our tickets
                $default_ticket_type = $this->get_ticket_type_id();
                if (!is_null($default_ticket_type)) {
                    set_feed_url('?mode=tickets&select=' . strval($default_ticket_type));
                }
                $this->ticket_type_id = $default_ticket_type;
            }

            $this->title = get_screen_title('SUPPORT_TICKETS');
        }

        if ($type == 'ticket') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('SUPPORT_TICKETS'))));

            $GLOBALS['OUTPUT_STREAMING'] = false; // Too complex to do a pre_run for this properly
        }

        if ($type == 'post') {
            $this->title = get_screen_title('SUPPORT_TICKETS');
        }

        if ($type == 'set_ticket_extra_access' || $type == '_set_ticket_extra_access') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('SUPPORT_TICKETS')), array('_SELF:_SELF:ticket:' . get_param_string('id'), do_lang_tempcode('VIEW_SUPPORT_TICKET'))));

            $this->title = get_screen_title('SET_TICKET_EXTRA_ACCESS');
        }

        if ($type == 'toggle_ticket_closed') {
            $GLOBALS['OUTPUT_STREAMING'] = false; // Too complex to do a pre_run for this properly
        }

        if ($type == 'merge' || $type == '_merge') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('SUPPORT_TICKETS')), array('_SELF:_SELF:ticket:' . get_param_string('from'), do_lang_tempcode('VIEW_SUPPORT_TICKET'))));

            $this->title = get_screen_title('MERGE_SUPPORT_TICKETS');
        }

        if ($type == 'assign') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('SUPPORT_TICKETS')), array('_SELF:_SELF:ticket:' . get_param_string('ticket_id'), do_lang_tempcode('VIEW_SUPPORT_TICKET'))));

            $this->title = get_screen_title('TICKET_ASSIGN');
        }

        if ($type == 'unassign') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('SUPPORT_TICKETS')), array('_SELF:_SELF:ticket:' . get_param_string('ticket_id'), do_lang_tempcode('VIEW_SUPPORT_TICKET'))));

            $this->title = get_screen_title('TICKET_UNASSIGN');
        }

        if ($type == 'edit' || $type == '_edit') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('SUPPORT_TICKETS')), array('_SELF:_SELF:ticket:' . get_param_string('id'), do_lang_tempcode('VIEW_SUPPORT_TICKET'))));

            $this->title = get_screen_title('EDIT_TICKET');
        }

        return null;
    }

    /**
     * Execute the module.
     *
     * @return Tempcode The result of execution.
     */
    public function run()
    {
        if (has_no_forum()) {
            warn_exit(do_lang_tempcode('NO_FORUM_INSTALLED'));
        }

        require_javascript('checking');
        require_css('tickets');
        require_code('tickets');
        require_code('tickets2');

        $type = get_param_string('type', 'browse');

        if ($type == 'browse') {
            return $this->do_choose_ticket();
        }
        if ($type == 'ticket') {
            return $this->do_ticket();
        }
        if ($type == 'post') {
            return $this->do_update_ticket();
        }
        if ($type == 'toggle_ticket_closed') {
            return $this->toggle_ticket_closed();
        }
        if ($type == 'set_ticket_extra_access') {
            return $this->set_ticket_extra_access();
        }
        if ($type == '_set_ticket_extra_access') {
            return $this->_set_ticket_extra_access();
        }
        if ($type == 'merge') {
            return $this->merge();
        }
        if ($type == '_merge') {
            return $this->_merge();
        }
        if ($type == 'assign') {
            return $this->assign();
        }
        if ($type == 'unassign') {
            return $this->unassign();
        }
        if ($type == 'edit') {
            return $this->edit();
        }
        if ($type == '_edit') {
            return $this->_edit();
        }

        return new Tempcode();
    }

    /**
     * Find the selected ticket type ID.
     *
     * @return ?AUTO_LINK The ticket type ID (null: none specified)
     */
    private function get_ticket_type_id()
    {
        $default_ticket_type = either_param_integer('ticket_type_id', null);
        if (is_null($default_ticket_type)) {
            $_default_ticket_type = either_param_string('ticket_type', null);
            if (!is_null($_default_ticket_type)) {
                $default_ticket_type = $GLOBALS['SITE_DB']->query_select_value_if_there('ticket_types', 'id', array($GLOBALS['SITE_DB']->translate_field_ref('ticket_type_name') => $_default_ticket_type));
                if (is_null($default_ticket_type)) {
                    warn_exit(do_lang_tempcode('CAT_NOT_FOUND', escape_html($_default_ticket_type), 'ticket_type'));
                }
            }
        }
        return $default_ticket_type;
    }

    /**
     * The UI to show support tickets we may view.
     *
     * @return Tempcode The UI
     */
    public function do_choose_ticket()
    {
        require_code('feedback');

        $message = new Tempcode();
        $links = new Tempcode();
        $existing_ticket_types = array();

        if (!is_guest()) {
            // Our tickets
            $ticket_type_id = $this->ticket_type_id;
            $tickets = get_tickets(get_member(), $ticket_type_id, false, false, get_param_integer('open', 0) == 1);

            // Find all ticket types used
            if (is_null($ticket_type_id)) {
                $all_tickets = $tickets;
            } else {
                $all_tickets = get_tickets(get_member(), null, false, false, get_param_integer('open', 0) == 1);
            }
            foreach ($all_tickets as $topic) {
                $ticket_id = extract_topic_identifier($topic['description']);
                $ticket_type_id = $GLOBALS['SITE_DB']->query_select_value_if_there('tickets', 'ticket_type', array('ticket_id' => $ticket_id));
                if (!is_null($ticket_type_id)) {
                    $existing_ticket_types[] = $ticket_type_id;
                }
            }

            // List (our?) tickets
            if (!is_null($tickets)) {
                if (has_privilege(get_member(), 'support_operator')) {
                    $message = do_lang_tempcode('TICKETS_STAFF');
                } else {
                    $message = do_lang_tempcode('TICKETS_MEMBER');
                }

                foreach ($tickets as $topic) {
                    if (($topic['closed']) && (has_privilege(get_member(), 'support_operator')) && (count($tickets) > 3)) {
                        continue; // Staff don't see closed tickets
                    }

                    list($ticket_type_tpl) = $this->_render_ticket_row($topic);

                    $links->attach($ticket_type_tpl);
                }
            }
        } else {
            $_login_url = build_url(array('page' => 'login'));
            $login_url = $_login_url->evaluate();
            $message = do_lang_tempcode('NO_TICKETS_GUESTS', escape_html($login_url));
            $tickets = array();
        }

        $map = array('page' => '_SELF', 'type' => 'ticket');
        $default_ticket_type = $this->get_ticket_type_id();
        if ($default_ticket_type !== null) {
            $map['ticket_type_id'] = $default_ticket_type;
        }
        $add_ticket_url = build_url($map, '_SELF');

        $tpl = do_template('SUPPORT_TICKETS_SCREEN', array(
            '_GUID' => 'b208a9f1504d6b8a76400d89a8265d91',
            'TITLE' => $this->title,
            'MESSAGE' => $message,
            'LINKS' => $links,
            'ADD_TICKET_URL' => $add_ticket_url,
            'TYPES' => build_types_list($default_ticket_type),
        ));

        require_code('templates_internalise_screen');
        return internalise_own_screen($tpl, 30, $tickets);
    }

    /**
     * Render a ticket link row.
     *
     * @param  array $topic Ticket details (from forum API)
     * @return array A tuple: Ticket row (Tempcode), Ticket type (ID), Ticket type (String)
     */
    public function _render_ticket_row($topic)
    {
        $ticket_id = extract_topic_identifier($topic['description']);

        $url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $ticket_id), '_SELF');

        $title = $topic['firsttitle'];

        $first_date = get_timezoned_date($topic['firsttime']);
        $first_poster_id = isset($topic['firstmemberid']) ? $topic['firstmemberid'] : $GLOBALS['FORUM_DRIVER']->get_member_from_username($topic['firstusername']);
        $first_poster_profile_url = '';
        $first_poster = do_lang('UNKNOWN');
        if (!is_null($first_poster_id)) {
            $first_poster_profile_url = $GLOBALS['FORUM_DRIVER']->member_profile_url($first_poster_id, false, true);
            $first_poster = $topic['firstusername'];
        }

        $last_date = get_timezoned_date($topic['lasttime']);
        $last_poster_id = isset($topic['lastmemberid']) ? $topic['lastmemberid'] : $GLOBALS['FORUM_DRIVER']->get_member_from_username($topic['lastusername']);
        $last_poster = do_lang('UNKNOWN');
        $last_poster_profile_url = '';
        if (!is_null($last_poster_id)) {
            $last_poster_profile_url = $GLOBALS['FORUM_DRIVER']->member_profile_url($last_poster_id, false, true);
            $last_poster = $topic['lastusername'];
        }

        $ticket_type_id = $GLOBALS['SITE_DB']->query_select_value_if_there('tickets', 'ticket_type', array('ticket_id' => $ticket_id));
        if (is_null($ticket_type_id)) {
            $ticket_type_name = do_lang('UNKNOWN');
        } else {
            $ticket_type_details = get_ticket_type($ticket_type_id);
            $ticket_type_name = get_translated_text($ticket_type_details['ticket_type_name']);
        }

        $assigned = find_ticket_assigned_to($ticket_id);

        if (function_exists('get_composr_support_timings_wrap')) { // FUDGE. Extra code may be added in for compo.sr's ticket system
            $extra_details = get_composr_support_timings_wrap($topic['closed'] == 0, $topic['id'], $ticket_type_name);
        } else {
            $extra_details = new Tempcode();
        }

        $tpl = do_template('SUPPORT_TICKET_LINK', array(
            '_GUID' => '4a39a6b5a7d56ead2d9c20b8a7a71398',
            'NUM_POSTS' => integer_format($topic['num'] - 1),
            'CLOSED' => strval($topic['closed']),
            'URL' => $url,
            'ID' => $ticket_id,
            'TITLE' => $title,
            'EXTRA_DETAILS' => $extra_details,
            'TICKET_TYPE_NAME' => $ticket_type_name,
            'TICKET_TYPE_ID' => is_null($ticket_type_id) ? '' : strval($ticket_type_id),
            'FIRST_DATE' => $first_date,
            'FIRST_DATE_RAW' => strval($topic['firsttime']),
            'FIRST_POSTER_PROFILE_URL' => $first_poster_profile_url,
            'FIRST_POSTER' => $first_poster,
            'FIRST_POSTER_ID' => strval($first_poster_id),
            'LAST_DATE' => $last_date,
            'LAST_DATE_RAW' => strval($topic['lasttime']),
            'LAST_POSTER_PROFILE_URL' => $last_poster_profile_url,
            'LAST_POSTER' => $last_poster,
            'LAST_POSTER_ID' => strval($last_poster_id),
            'ASSIGNED' => $assigned,
        ));

        return array($tpl, $ticket_type_id, $ticket_type_name);
    }

    /**
     * The UI to either show an existing ticket and allow a reply, or to start a new ticket.
     *
     * @return Tempcode The UI
     */
    public function do_ticket()
    {
        require_lang('comcode');

        $id = get_param_string('id', null);
        if ($id == '') {
            $id = null;
        }
        if (!is_null($id)) { // Existing ticket
            $_temp = explode('_', $id);
            if (!isset($_temp[1])) {
                warn_exit(do_lang_tempcode('INTERNAL_ERROR')); // Normal topic, not a ticket!
            }
            $ticket_owner = intval($_temp[0]);
            $ticket_id = $_temp[1];

            check_ticket_access($id);

            $test_username = $GLOBALS['FORUM_DRIVER']->get_username($ticket_owner);
            if ($test_username === null) {
                $ticket_owner = get_member();
            }
        } else { // New ticket, generate an ID
            if (has_privilege(get_member(), 'support_operator')) {
                $ticket_owner = get_param_integer('post_as', get_member());
            } else {
                $ticket_owner = get_member();
            }
            $ticket_id = uniqid('', false);
        }

        $poster = '';
        $new = true;
        $serialized_options = mixed();
        $hash = mixed();
        $ticket_type_id = mixed();
        if ((!is_guest()) || (is_null($id))) { // If this isn't a guest posting their ticket
            $new = is_null($id);

            $num_to_show_limit = get_param_integer('max_comments', intval(get_option('comments_to_show_in_thread')));
            $start = get_param_integer('start_comments', 0);

            // Find existing posts/info
            if ($new) {
                $id = strval($ticket_owner) . '_' . $ticket_id;
                if ($ticket_owner != get_member()) {
                    $this->title = get_screen_title('ADD_TICKET_AS', true, array(escape_html($GLOBALS['FORUM_DRIVER']->get_username($ticket_owner))));
                } else {
                    $this->title = get_screen_title('ADD_TICKET');
                }

                $_comments = array();
                $_comments_all = array();
            } else {
                $ticket_type_id = $GLOBALS['SITE_DB']->query_select_value_if_there('tickets', 'ticket_type', array('ticket_id' => $id));
                $ticket_type_details = get_ticket_type($ticket_type_id);
                $ticket_type_name = get_translated_text($ticket_type_details['ticket_type_name']);

                $forum = 1;
                $topic_id = 1;
                $_ticket_type_id = 1; // These will be returned by reference
                $_comments = get_ticket_posts($id, $forum, $topic_id, $_ticket_type_id, $start, $num_to_show_limit);
                $_comments_all = get_ticket_posts($id, $forum, $topic_id, $_ticket_type_id);
                if ((!is_array($_comments)) || (!array_key_exists(0, $_comments))) {
                    warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'ticket'));
                }

                $ticket_title = $_comments[0]['title'];
                if ($ticket_title == '') {
                    $ticket_title = do_lang('UNKNOWN');
                }

                $this->title = get_screen_title('_VIEW_SUPPORT_TICKET', true, array(escape_html($ticket_title), escape_html($ticket_type_name)));
                breadcrumb_set_self($ticket_title);
            }

            // Help text
            $ticket_page_text = comcode_to_tempcode(get_option('ticket_text'), null, true);

            // Selection of ticket type
            $default_ticket_type = $this->get_ticket_type_id();
            $types = build_types_list($default_ticket_type);

            // Render existing posts/info
            $pagination = null;
            $staff_details = new Tempcode();
            if (!$new) {
                if (is_null($_comments)) {
                    warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'ticket'));
                }
                if (has_privilege(get_member(), 'support_operator')) {
                    $topic_url = $GLOBALS['FORUM_DRIVER']->topic_url($topic_id, get_option('ticket_forum_name'), true);
                    $staff_details = is_object($topic_url) ? $topic_url : make_string_tempcode($topic_url);
                } else {
                    $staff_details = new Tempcode();
                }

                require_code('topics');
                $renderer = new CMS_Topic();
                $renderer->set_rendering_context('tickets');
                $renderer->inject_posts_for_scoring_algorithm($_comments);
                $renderer->topic_id = $topic_id;

                $topic_info = mixed();
                if (get_forum_type() == 'cns') {
                    $_topic_info = $GLOBALS['FORUM_DB']->query_select('f_topics', array('*'), array('id' => $topic_id), '', 1);
                    if (!array_key_exists(0, $_topic_info)) {
                        warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
                    }
                    $topic_info = $_topic_info[0];
                }

                // Posts
                $max_thread_depth = get_param_integer('max_thread_depth', intval(get_option('max_thread_depth')));
                list($comments, $serialized_options, $hash) = $renderer->render_posts($num_to_show_limit, $max_thread_depth, true, $ticket_owner, array(), $forum, $topic_info);

                // Pagination
                if (!$renderer->is_threaded) {
                    if (count($_comments_all) > $num_to_show_limit) {
                        require_code('templates_pagination');
                        $pagination = pagination(do_lang_tempcode('COMMENTS'), $start, 'start_comments', $num_to_show_limit, 'max_comments', count($_comments_all));
                    }
                }

                set_extra_request_metadata(array(
                    'created' => date('Y-m-d', $_comments_all[0]['date']),
                    'creator' => $GLOBALS['FORUM_DRIVER']->get_username($_comments_all[0]['member']),
                    'type' => 'Support ticket',
                    'title' => $_comments_all[0]['title'],
                    'identifier' => '_SEARCH:tickets:ticket:' . $id,
                    'image' => find_theme_image('icons/48x48/menu/site_meta/tickets'),
                ));

                // "Staff only reply" tickbox
                $has_staff_only = ((get_forum_type() == 'cns') && ($GLOBALS['FORUM_DRIVER']->is_staff(get_member())));
            } else {
                $comments = new Tempcode();
                $has_staff_only = false;
                $ticket_type_details = get_ticket_type($default_ticket_type);
            }

            // Posting form
            if (($poster == '') || ($GLOBALS['FORUM_DRIVER']->get_guest_id() != intval($poster))) { // We can post a new ticket reply to an existing ticket that isn't from a guest
                $em = $GLOBALS['FORUM_DRIVER']->get_emoticon_chooser();
                require_code('form_templates');
                list($attachments, $attach_size_field) = (get_forum_type() == 'cns') ? get_attachments('post') : array(null, null);
                if (addon_installed('captcha')) {
                    require_code('captcha');
                    $use_captcha = ((get_option('captcha_on_feedback') == '1') && (use_captcha()));
                    if ($use_captcha) {
                        generate_captcha();
                    }
                } else {
                    $use_captcha = false;
                }

                $comment_form = do_template('COMMENTS_POSTING_FORM', array(
                    '_GUID' => 'aaa32620f3eb68d9cc820b18265792d7',
                    'DEFAULT_TEXT' => either_param_string('post', null),
                    'JOIN_BITS' => '',
                    'FIRST_POST_URL' => '',
                    'FIRST_POST' => '',
                    'USE_CAPTCHA' => $use_captcha,
                    'ATTACHMENTS' => $attachments,
                    'ATTACH_SIZE_FIELD' => $attach_size_field,
                    'POST_WARNING' => '',
                    'COMMENT_TEXT' => '',
                    'GET_EMAIL' => is_guest(),
                    'EMAIL_OPTIONAL' => ((is_guest()) && ($ticket_type_details['guest_emails_mandatory'] == 0)),
                    'GET_TITLE' => $new,
                    'EM' => $em,
                    'DISPLAY' => 'block',
                    'COMMENT_URL' => '',
                    'SUBMIT_NAME' => do_lang_tempcode($new ? 'CREATE_SUPPORT_TICKET' : 'MAKE_POST'),
                    'TITLE' => do_lang_tempcode($new ? 'CREATE_TICKET_MAKE_POST' : 'REPLY'),
                ));
            } else {
                $comment_form = new Tempcode();
            }

            // Show other tickets
            require_code('form_templates');
            require_code('feedback');
            list($warning_details, $ping_url) = handle_conflict_resolution(null, true);
            $other_tickets = new Tempcode();
            $our_topic = null;
            $type_activity_overview = array();
            if (!is_guest($ticket_owner)) {
                $tickets_of_member = get_tickets($ticket_owner, null, true);
                if (!is_null($tickets_of_member)) {
                    foreach ($tickets_of_member as $topic) {
                        $_id = extract_topic_identifier($topic['description']);

                        list($other_ticket_tpl, $ticket_type_id, $ticket_type_name) = $this->_render_ticket_row($topic);

                        if (!isset($type_activity_overview[$ticket_type_id])) {
                            $type_activity_overview[$ticket_type_id] = array(
                                'OVERVIEW_TYPE' => $ticket_type_name,
                                'OVERVIEW_COUNT' => '0',
                            );
                        }
                        $type_activity_overview[$ticket_type_id]['OVERVIEW_COUNT'] = strval(intval($type_activity_overview[$ticket_type_id]['OVERVIEW_COUNT']) + 1);

                        if ($id != $_id) {
                            $other_tickets->attach($other_ticket_tpl);
                        } else {
                            $our_topic = $topic;
                        }
                    }
                    sort_maps_by($type_activity_overview, 'OVERVIEW_TYPE');
                }
            }

            // Is it closed?
            $closed = is_null($our_topic) ? false : ($our_topic['closed'] == 1);
            $toggle_ticket_closed_url = mixed();
            if ((get_forum_type() == 'cns') && (!$new)) {
                $toggle_ticket_closed_url = build_url(array('page' => '_SELF', 'type' => 'toggle_ticket_closed', 'id' => $id), '_SELF');
            }
            if ($closed) {
                $new_ticket_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'ticket_type_id' => $ticket_type_id), '_SELF');
                attach_message(do_lang_tempcode('TICKET_IS_CLOSED', escape_html($new_ticket_url->evaluate())), 'notice');
            }

            // URL To add a new ticket
            $add_ticket_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'ticket_type_id' => $default_ticket_type), '_SELF');

            // Link to edit ticket subject/type
            $edit_url = mixed();
            if (!$new) {
                $edit_url = build_url(array('page' => '_SELF', 'type' => 'edit', 'id' => $id), '_SELF');
            }

            // Link to set ticket extra access
            $set_ticket_extra_access_url = mixed();
            if (!$new) {
                $set_ticket_extra_access_url = build_url(array('page' => '_SELF', 'type' => 'set_ticket_extra_access', 'id' => $id), '_SELF');
            }

            // Post templates
            $post_templates = new Tempcode();
            if (($has_staff_only) && (addon_installed('cns_post_templates')) && (get_forum_type() == 'cns')) {
                require_code('cns_posts_action');
                require_lang('cns_post_templates');

                $forum_id = get_ticket_forum_id($ticket_owner, $ticket_type_id);

                $templates = cns_get_post_templates($forum_id);
                $_post_templates = new Tempcode();
                foreach ($templates as $template) {
                    list($pt_title, $pt_text,) = $template;
                    $_post_templates->attach(form_input_list_entry(str_replace("\n", '\n', $pt_text), false, $pt_title));
                }
                if ((!$_post_templates->is_empty()) && (has_js())) {
                    $post_templates2 = form_input_list_entry('', false, do_lang_tempcode('NA_EM'));
                    $post_templates2->attach($_post_templates);

                    $post_templates = do_template('CNS_POST_TEMPLATE_SELECT', array('_GUID' => 'b670b322b96041db458057432e33cdca', 'SKIP_LABEL' => true, 'LIST' => $post_templates2, 'RESETS' => true));
                }
            }

            $assigned = find_ticket_assigned_to($id);

            $extra_details = new Tempcode();
            if (function_exists('get_composr_support_timings_wrap')) { // FUDGE. Extra code may be added in for compo.sr's ticket system
                if (!$new) {
                    $last_poster_id = isset($our_topic['lastmemberid']) ? $our_topic['lastmemberid'] : $GLOBALS['FORUM_DRIVER']->get_member_from_username($our_topic['lastusername']);
                    $extra_details = get_composr_support_timings_wrap($our_topic['closed'] == 0, $our_topic['id'], $ticket_type_name, true);
                }
            }

            // Render ticket screen
            $post_url = build_url(array('page' => '_SELF', 'type' => 'post', 'id' => $id, 'redirect' => get_param_string('redirect', null), 'start_comments' => get_param_string('start_comments', null), 'max_comments' => get_param_string('max_comments', null)), '_SELF');
            $tpl = do_template('SUPPORT_TICKET_SCREEN', array(
                '_GUID' => 'd21a9d161008c6c44fe7309a14be2c5b',
                'ID' => is_null($id) ? '' : $id,
                'SERIALIZED_OPTIONS' => $serialized_options,
                'HASH' => $hash,
                'TOGGLE_TICKET_CLOSED_URL' => $toggle_ticket_closed_url,
                'CLOSED' => $closed,
                'OTHER_TICKETS' => $other_tickets,
                'USERNAME' => $GLOBALS['FORUM_DRIVER']->get_username($ticket_owner),
                'TICKET_TYPE_ID' => is_null($ticket_type_id) ? null : strval($ticket_type_id),
                'PING_URL' => $ping_url,
                'WARNING_DETAILS' => $warning_details,
                'NEW' => $new,
                'TICKET_PAGE_TEXT' => $ticket_page_text,
                'POST_TEMPLATES' => $post_templates,
                'TYPES' => $types,
                'STAFF_ONLY' => $has_staff_only,
                'POSTER' => $poster,
                'TITLE' => $this->title,
                'COMMENTS' => $comments,
                'COMMENT_FORM' => $comment_form,
                'STAFF_DETAILS' => $staff_details,
                'URL' => $post_url,
                'ADD_TICKET_URL' => $add_ticket_url,
                'PAGINATION' => $pagination,
                'TYPE_ACTIVITY_OVERVIEW' => $type_activity_overview,
                'SET_TICKET_EXTRA_ACCESS_URL' => $set_ticket_extra_access_url,
                'EDIT_URL' => $edit_url,
                'ASSIGNED' => $assigned,
                'EXTRA_DETAILS' => $extra_details,
            ));

            require_code('templates_internalise_screen');
            return internalise_own_screen($tpl, 30, is_array($_comments_all) ? count($_comments_all) : 0);
        } else { // Guest has posted ticket successfully. Actually, this code problem never runs (as they in fact see a separate screen from do_update_ticket), but it's here as a fail safe.
            return inform_screen(get_screen_title('ADD_TICKET'), do_lang_tempcode('SUCCESS'));
        }
    }

    /**
     * Actualise to toggle the closed state of a ticket.
     *
     * @return Tempcode The UI
     */
    public function toggle_ticket_closed()
    {
        $id = get_param_string('id');

        require_code('feedback');

        $action = 'CLOSE_TICKET';

        // Our tickets - search them for this ticket, acting as a kind of security check (as we will only iterate through tickets we have access to)
        $tickets = get_tickets(get_member(), null);
        foreach ($tickets as $ticket) {
            $_id = extract_topic_identifier($ticket['description']);
            if ($_id == $id) {
                if ($ticket['closed'] == 0) {
                    $action = 'OPEN_TICKET';
                }
                $GLOBALS['FORUM_DB']->query_update('f_topics', array('t_is_open' => $ticket['closed']), array('id' => $ticket['id']), '', 1);
            }
        }

        $this->title = get_screen_title($action);

        $url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $id), '_SELF');
        if (is_guest()) {
            $url = build_url(array('page' => '_SELF'), '_SELF');
        }
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Actualise ticket creation/reply, then show the ticket again.
     *
     * @return Tempcode The UI
     */
    public function do_update_ticket()
    {
        @ignore_user_abort(true); // Must keep going till completion

        $id = get_param_string('id', strval(get_member()) . '_' . uniqid('', false)/*random new ticket ID*/);

        $ticket_type_id = $this->get_ticket_type_id();

        if ($ticket_type_id === null) {
            // If existing ticket, check access
            check_ticket_access($id);
        }

        $_title = post_param_string('title');

        $post = post_param_string('post', '');
        if ($post == '') {
            // Maybe we are relaying data into the ticket from elsewhere?
            require_code('mail');
            $details = _form_to_email(array('title', 'ticket_type_id', 'ticket_type', 'staff_only', 'faq_searched', 'email', 'close'));
            list(, $post) = $details;
            if ($post == '') {
                warn_exit(do_lang_tempcode('NO_PARAMETER_SENT', 'post'));
            }
        }

        $staff_only = post_param_integer('staff_only', 0) == 1;

        // Update
        $_home_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $id, 'redirect' => null), '_SELF', null, false, true, true);
        $home_url = $_home_url->evaluate();
        $email = '';
        if ($ticket_type_id !== null) { // New ticket
            $ticket_type_details = get_ticket_type($ticket_type_id);

            if (!has_category_access(get_member(), 'tickets', strval($ticket_type_id))) {
                access_denied('I_ERROR');
            }

            // Check FAQ search results first
            if (($ticket_type_details['search_faq']) && (post_param_integer('faq_searched', 0) == 0)) {
                $results = $this->do_search($this->title, $id, $post);
                if (!is_null($results)) {
                    return $results;
                }
            }

            $new_post = new Tempcode();
            $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);
                }
                $new_post->attach(do_lang('GUEST_TICKET_REPLY_LINK', comcode_escape(post_param_string('title')), comcode_escape(get_site_name()), array(comcode_escape($body), $email)));
            } elseif ((is_guest()) && ($ticket_type_details['guest_emails_mandatory'] == 1)) {
                // Error if the e-mail address is required for this ticket type
                warn_exit(do_lang_tempcode('ERROR_GUEST_EMAILS_MANDATORY'));
            }
            $new_post->attach($post);
            $post = $new_post->evaluate();
        }
        if (addon_installed('captcha')) {
            if (get_option('captcha_on_feedback') == '1') {
                require_code('captcha');
                enforce_captcha();
            }
        }
        ticket_add_post(null, $id, $ticket_type_id, $_title, $post, $home_url, $staff_only);

        // Auto-monitor
        if (has_privilege(get_member(), 'support_operator') && get_option('ticket_auto_assign') == '1') {
            require_code('notifications');
            enable_notifications('ticket_assigned_staff', $id);
        }

        // Find true ticket title
        list($__title, $_topic_id) = get_ticket_details($id);

        // Send email
        if (!$staff_only) {
            if ($email == '') {
                $email = $GLOBALS['FORUM_DRIVER']->get_member_email_address(get_member());
            }
            send_ticket_email($id, $__title, $post, $home_url, ($ticket_type_id !== null) ? $email : '', $ticket_type_id, null);
        }

        // Close ticket, if requested
        if (post_param_integer('close', 0) == 1) {
            if (get_forum_type() == 'cns') {
                $GLOBALS['FORUM_DB']->query_update('f_topics', array('t_is_open' => 0), array('id' => $_topic_id), '', 1);
            }
        }

        $url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $id), '_SELF');
        if (is_guest()) {
            $url = build_url(array('page' => '_SELF'), '_SELF');
        }
        if (get_param_string('redirect', '') != '') {
            $url = make_string_tempcode(get_param_string('redirect'));
        }
        return redirect_screen($this->title, $url, do_lang_tempcode('TICKET_STARTED'));
    }

    /**
     * Check for existing FAQs matching a ticket to be submitted, via searching.
     *
     * @param  Tempcode $title Page title
     * @param  string $ticket_id Ticket ID we'd be creating
     * @param  string $content What is being searched for
     * @return ?Tempcode The search results (null: could not search)
     */
    public function do_search($title, $ticket_id, $content)
    {
        if (!addon_installed('catalogues')) {
            return null;
        }
        if (!addon_installed('search')) {
            return null;
        }

        require_code('database_search');
        require_code('search');

        // We don't want to display too many --- just enough to show the top results
        $max = 10;

        // Search under all hooks we've asked to search under
        $results = array();
        require_code('hooks/modules/search/catalogue_entries');
        $object = object_factory('Hook_search_catalogue_entries');
        $info = $object->info();
        if (is_null($info)) {
            return null;
        }

        // Get the ID of the default FAQ catalogue
        $catalogue_id = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'id', array('c_name' => 'faqs'), '');
        if (is_null($catalogue_id)) {
            return null;
        }

        // Category filter
        $where_clause = 'r.' . $info['category'] . '=' . strval($catalogue_id);
        $boolean_operator = 'OR';
        list($content_where) = build_content_where($content, true, $boolean_operator);
        $hook_results = $object->run($content, false, 'ASC', $max, 0, false, $content_where, '', null, null, 'relevance', null, $boolean_operator, $where_clause, null, true);
        if ((is_null($hook_results)) || (count($hook_results) == 0)) {
            return null;
        }

        foreach ($hook_results as $i => $result) {
            $result['object'] = $object;
            $result['type'] = 'catalogue_entries';
            $hook_results[$i] = $result;
        }

        $results = sort_search_results($hook_results, array(), 'ASC');
        $out = build_search_results_interface($results, 0, $max, 'ASC');

        return do_template('SUPPORT_TICKETS_SEARCH_SCREEN', array(
            '_GUID' => '427e28208e15494a8f126eb4fb2aa60c',
            'TITLE' => $title,
            'URL' => build_url(array('page' => '_SELF', 'type' => 'post', 'id' => $ticket_id), '_SELF'),
            'POST_FIELDS' => build_keep_post_fields(),
            'RESULTS' => $out,
        ));
    }

    /**
     * UI for setting ticket access.
     *
     * @return Tempcode The UI
     */
    public function set_ticket_extra_access()
    {
        require_code('form_templates');

        $id = get_param_string('id');

        $ticket_owner = check_ticket_access($id);
        $ticket_owner_username = $GLOBALS['FORUM_DRIVER']->get_username($ticket_owner);
        if (is_null($ticket_owner_username)) {
            $ticket_owner_username = do_lang('UNKNOWN');
        }

        $post_url = build_url(array('page' => '_SELF', 'type' => '_set_ticket_extra_access', 'id' => $id), '_SELF');

        $submit_name = do_lang_tempcode('SET_TICKET_EXTRA_ACCESS');

        $text = do_lang_tempcode('DESCRIPTION_SET_TICKET_EXTRA_ACCESS', escape_html($ticket_owner_username));

        $fields = new Tempcode();

        $access = array();
        $_access = $GLOBALS['SITE_DB']->query_select('ticket_extra_access', array('member_id'), array('ticket_id' => $id));
        foreach ($_access as $a) {
            $username = $GLOBALS['FORUM_DRIVER']->get_username($a['member_id']);
            if ($username !== null) {
                $access[] = $username;
            }
        }
        $fields->attach(form_input_username_multi(do_lang_tempcode('USERNAME'), '', 'access', $access, 0, true));

        return do_template('FORM_SCREEN', array('_GUID' => 'a94d516fc14662908628ff19eb7305b9', 'TITLE' => $this->title, 'HIDDEN' => '', 'TEXT' => $text, 'FIELDS' => $fields, 'SUBMIT_ICON' => 'menu__adminzone__security__permissions__privileges', 'SUBMIT_NAME' => $submit_name, 'URL' => $post_url));
    }

    /**
     * Actualiser for setting ticket access.
     *
     * @return Tempcode The UI
     */
    public function _set_ticket_extra_access()
    {
        $id = get_param_string('id');

        check_ticket_access($id);

        $GLOBALS['SITE_DB']->query_delete('ticket_extra_access', array(
            'ticket_id' => $id,
        ));

        foreach ($_POST as $key => $username) {
            if (substr($key, 0, strlen('access_')) != 'access_') {
                continue;
            }
            if ($username == '') {
                continue;
            }

            if (@get_magic_quotes_gpc()) {
                $username = stripslashes($username);
            }
            $member_id = $GLOBALS['FORUM_DRIVER']->get_member_from_username($username);
            if (is_null($member_id)) {
                continue;
            }

            $GLOBALS['SITE_DB']->query_insert('ticket_extra_access', array(
                'ticket_id' => $id,
                'member_id' => $member_id,
            ));
        }

        $url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $id), '_SELF');
        if (is_guest()) {
            $url = build_url(array('page' => '_SELF'), '_SELF');
        }
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * UI for editing a ticket type.
     *
     * @return Tempcode The UI
     */
    public function edit()
    {
        require_code('form_templates');

        $id = get_param_string('id');

        check_ticket_access($id);

        $post_url = build_url(array('page' => '_SELF', 'type' => '_edit', 'id' => $id), '_SELF');

        require_code('tickets2');
        list($title) = get_ticket_details($id);

        $submit_name = do_lang_tempcode('EDIT_TICKET');

        $text = '';

        $fields = new Tempcode();

        $selected_ticket_type_id = $GLOBALS['SITE_DB']->query_select_value('tickets', 'ticket_type', array('ticket_id' => $id));

        if (get_forum_type() == 'cns') {
            $fields->attach(form_input_line(do_lang_tempcode('SUBJECT'), '', 'title', $title, true));
        }

        $ticket_types = build_types_list($selected_ticket_type_id, array($selected_ticket_type_id));
        $_ticket_types = new Tempcode();
        foreach ($ticket_types as $ticket_type) {
            $_ticket_types->attach(form_input_list_entry($ticket_type['TICKET_TYPE_ID'], $ticket_type['SELECTED'], $ticket_type['NAME']));
        }
        $fields->attach(form_input_list(do_lang_tempcode('TICKET_TYPE'), '', 'ticket_type', $_ticket_types));

        return do_template('FORM_SCREEN', array('_GUID' => '1fd8f7eaf5346776ae2362450d81f4c2', 'TITLE' => $this->title, 'HIDDEN' => '', 'TEXT' => $text, 'FIELDS' => $fields, 'SUBMIT_ICON' => 'buttons__save', 'SUBMIT_NAME' => $submit_name, 'URL' => $post_url));
    }

    /**
     * Actualiser for setting ticket access.
     *
     * @return Tempcode The UI
     */
    public function _edit()
    {
        $id = get_param_string('id');

        check_ticket_access($id);

        $old_ticket_type = $GLOBALS['SITE_DB']->query_select_value('tickets', 'ticket_type', array('ticket_id' => $id));
        $ticket_type = post_param_integer('ticket_type');

        require_code('tickets2');
        list($title, , , $uid) = get_ticket_details($id);

        if ($old_ticket_type != $ticket_type) {
            // Tell the staff that the ticket has been re-routed

            require_code('notifications');
            $username = $GLOBALS['FORUM_DRIVER']->get_username($uid);
            if (is_null($username)) {
                $username = do_lang('UNKNOWN');
            }
            $ticket_type_details = get_ticket_type($ticket_type);
            $ticket_type_name_new = get_translated_text($ticket_type_details['ticket_type_name']);
            $ticket_type_details = get_ticket_type($old_ticket_type);
            $ticket_type_name_old = get_translated_text($ticket_type_details['ticket_type_name']);

            $subject = do_lang('SUBJECT_TICKET_REROUTED', $title, $username, array($ticket_type_name_new, $ticket_type_name_old));
            $message = do_notification_lang('BODY_TICKET_REROUTED', comcode_escape($title), comcode_escape($username), array(comcode_escape($ticket_type_name_new), comcode_escape($ticket_type_name_old)));

            dispatch_notification(
                'ticket_new_staff',
                strval($ticket_type),
                $subject,
                $message
            );
        }

        $forum_id = get_ticket_forum_id(null, $ticket_type, true);

        $GLOBALS['SITE_DB']->query_update('tickets', array('ticket_type' => $ticket_type, 'forum_id' => $forum_id), array('ticket_id' => $id), '', 1);

        if (get_forum_type() == 'cns') {
            $title = post_param_string('title');

            $topic_id = $GLOBALS['SITE_DB']->query_select_value('tickets', 'topic_id', array('ticket_id' => $id));
            $post_id = $GLOBALS['FORUM_DB']->query_select_value('f_topics', 't_cache_first_post_id', array('id' => $topic_id));
            $post_id = $GLOBALS['FORUM_DB']->query_select_value('f_posts', 'MIN(id)', array('p_topic_id' => $topic_id), 'AND id<>' . strval($post_id));
            $GLOBALS['FORUM_DB']->query_update('f_topics', array('t_forum_id' => $forum_id, 't_cache_first_title' => $title), array('id' => $topic_id), '', 1);
            $GLOBALS['FORUM_DB']->query_update('f_posts', array('p_cache_forum_id' => $forum_id), array('p_topic_id' => $topic_id), '', 1);
            $GLOBALS['FORUM_DB']->query_update('f_posts', array('p_title' => $title), array('id' => $post_id), '', 1);
        }

        $url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $id), '_SELF');
        if (is_guest()) {
            $url = build_url(array('page' => '_SELF'), '_SELF');
        }
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * UI for merging one ticket into another.
     *
     * @return Tempcode The UI
     */
    public function merge()
    {
        if (!has_privilege(get_member(), 'support_operator')) {
            access_denied('I_ERROR');
        }

        require_code('tickets2');

        $from = get_param_string('from');
        $to = get_param_string('to');

        list($from_title) = get_ticket_details($from);
        list($to_title) = get_ticket_details($to);

        require_code('templates_confirm_screen');
        $preview = do_lang_tempcode('CONFIRM_MERGE_TICKETS', escape_html($from_title), escape_html($to_title));
        $action_url = build_url(array('page' => '_SELF', 'type' => '_merge', 'from' => $from, 'to' => $to), '_SELF');
        return confirm_screen($this->title, $preview, $action_url);
    }

    /**
     * Actualiser for merging one ticket into another.
     *
     * @return Tempcode The UI
     */
    public function _merge()
    {
        if (!has_privilege(get_member(), 'support_operator')) {
            access_denied('I_ERROR');
        }

        $from = get_param_string('from');
        $to = get_param_string('to');

        list($from_title, $from_topic_id) = get_ticket_details($from);
        list($to_title) = get_ticket_details($to);

        // Add into new ticket
        $_home_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $to, 'redirect' => null), '_SELF', null, false, true, true);
        $home_url = $_home_url->evaluate();
        $forum = 1;
        $topic_id = 1;
        $_ticket_type_id = 1; // These will be returned by reference
        $_comments_all = get_ticket_posts($from, $forum, $topic_id, $_ticket_type_id);
        if (count($_comments_all) == 0) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'ticket'));
        }
        foreach ($_comments_all as $comment) {
            ticket_add_post($comment['member'], $to, $_ticket_type_id, $comment['title'], $comment['message_comcode'], $home_url, isset($comment['staff_only']) && $comment['staff_only'], $comment['date']);
        }

        // Notification to support operator
        $subject = do_lang(
            'TICKETS_MERGED_INTO_SUBJECT',
            $from_title,
            $to_title,
            array(
                $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true),
                $GLOBALS['FORUM_DRIVER']->get_username(get_member())
            ),
            get_site_default_lang()
        );
        $message = do_notification_lang(
            'TICKETS_MERGED_INTO_BODY',
            comcode_escape($from_title),
            comcode_escape($to_title),
            array(
                comcode_escape($GLOBALS['FORUM_DRIVER']->get_username(get_member(), true)),
                $home_url,
                comcode_escape($GLOBALS['FORUM_DRIVER']->get_username(get_member()))
            ),
            get_site_default_lang()
        );
        dispatch_notification(
            'ticket_assigned_staff',
            $to,
            $subject,
            $message
        );

        // Add move notification post into from (old) ticket
        $_home_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $from, 'redirect' => null), '_SELF', null, false, true, true);
        $home_url = $_home_url->evaluate();
        $merge_title = do_lang('TICKETS_MERGED_TITLE');
        $merge_post = do_lang('TICKETS_MERGED_POST', $to_title);
        ticket_add_post(null, $from, $_ticket_type_id, $merge_title, $merge_post, $home_url, false);
        $email = $GLOBALS['FORUM_DRIVER']->get_member_email_address($_comments_all[0]['member']);
        send_ticket_email($from, $merge_title, $merge_post, $home_url, $email, null, null);

        // Closed old ticket
        if (get_forum_type() == 'cns') {
            $GLOBALS['FORUM_DB']->query_update('f_topics', array('t_is_open' => 0), array('id' => $from_topic_id), '', 1);
        }

        // Redirect
        require_code('templates_redirect_screen');
        $redirect_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $to), '_SELF');
        return redirect_screen($this->title, $redirect_url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Assign a ticket.
     *
     * @return Tempcode The UI
     */
    public function assign()
    {
        if (!has_privilege(get_member(), 'support_operator')) {
            access_denied('I_ERROR');
        }

        $ticket_id = get_param_string('ticket_id');

        list($ticket_title) = get_ticket_details($ticket_id);

        $username = post_param_string('username');
        $member_id = $GLOBALS['FORUM_DRIVER']->get_member_from_username($username);
        if (is_null($member_id)) {
            warn_exit(do_lang_tempcode('_MEMBER_NO_EXIST', escape_html($username)));
        }
        if (!has_privilege($member_id, 'support_operator')) {
            warn_exit(do_lang_tempcode('NOT_A_SUPPORT_OPERATOR', escape_html($username)));
        }

        require_code('notifications');
        enable_notifications('ticket_assigned_staff', $ticket_id, $member_id);

        // Notification to support operator that they are assigned
        $_home_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $ticket_id, 'redirect' => null), '_SELF', null, false, true, true);
        $home_url = $_home_url->evaluate();
        $subject = do_lang(
            'TICKET_ASSIGNED_SUBJECT',
            $ticket_title,
            $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true),
            array(
                $GLOBALS['FORUM_DRIVER']->get_username(get_member())
            ),
            get_site_default_lang()
        );
        $message = do_notification_lang(
            'TICKET_ASSIGNED_BODY',
            comcode_escape($ticket_title),
            comcode_escape($GLOBALS['FORUM_DRIVER']->get_username(get_member(), true)),
            array(
                $home_url,
                $GLOBALS['FORUM_DRIVER']->get_username(get_member())
            ),
            get_site_default_lang()
        );
        dispatch_notification(
            'ticket_assigned_staff',
            $ticket_id,
            $subject,
            $message,
            array($member_id)
        );

        // Redirect
        require_code('templates_redirect_screen');
        $redirect_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $ticket_id), '_SELF');
        return redirect_screen($this->title, $redirect_url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Unassign a ticket.
     *
     * @return Tempcode The UI
     */
    public function unassign()
    {
        if (!has_privilege(get_member(), 'support_operator')) {
            access_denied('I_ERROR');
        }

        $ticket_id = get_param_string('ticket_id');

        list($ticket_title) = get_ticket_details($ticket_id);

        $member_id = get_param_integer('member_id');

        require_code('notifications');

        // Notification to support operator that they are assigned
        $_home_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $ticket_id, 'redirect' => null), '_SELF', null, false, true, true);
        $home_url = $_home_url->evaluate();
        $subject = do_lang(
            'TICKET_UNASSIGNED_SUBJECT',
            $ticket_title,
            $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true),
            array(
                $GLOBALS['FORUM_DRIVER']->get_username(get_member())
            ),
            get_site_default_lang()
        );
        $message = do_notification_lang(
            'TICKET_UNASSIGNED_BODY',
            comcode_escape($ticket_title),
            comcode_escape($GLOBALS['FORUM_DRIVER']->get_username(get_member(), true)),
            array(
                $home_url,
                $GLOBALS['FORUM_DRIVER']->get_username(get_member())
            ),
            get_site_default_lang()
        );
        $_GET['keep_debug_notifications'] = '1'; // FUDGE: Force it to go out BEFORE we run disable_notifications
        dispatch_notification(
            'ticket_assigned_staff',
            $ticket_id,
            $subject,
            $message,
            array($member_id)
        );
        unset($_GET['keep_debug_notifications']);

        disable_notifications('ticket_assigned_staff', $ticket_id, $member_id);

        // Redirect
        require_code('templates_redirect_screen');
        $redirect_url = build_url(array('page' => '_SELF', 'type' => 'ticket', 'id' => $ticket_id), '_SELF');
        return redirect_screen($this->title, $redirect_url, do_lang_tempcode('SUCCESS'));
    }
}
