/**
 *	Return a list of possible users that can be assigned a ticket.
 *
 *	This function centralises who a ticket can be assigned to. Currently this is:
 *	- user with shd_staff permission
 *	- can see "any" ticket (not just their own)
 *	- additionally, if the ticket is private, the user must also be able to see 'any' private ticket.
 *	- additionally, check whether user is staff + ticket starter, and then whether we should block them from being assigned
 *	- (since 1.1) additionally if we have set the option, which users are true admins, and if so, remove them from the list too
 *
 *	@param bool $private Whether the ticket in question is private or not.
 *	@param int $ticket_owner User id of the ticket owner
 *	@param int $dept The department of the ticket, used for establishing permissions.
 *
 *	@return array An indexed array of member ids that this ticket could be assigned to.
 *	@see shd_assign()
 *	@see shd_assign2()
 *	@since 1.0
*/
function shd_get_possible_assignees($private = false, $ticket_owner = 0, $dept = -1)
{
    global $context, $smcFunc, $modSettings;
    // people who can handle a ticket
    $staff = shd_members_allowed_to('shd_staff', $dept);
    // is it private, if so, remove that list
    if ((bool) $private == true) {
        $private = shd_members_allowed_to('shd_view_ticket_private_any', $dept);
        $staff = array_intersect($staff, $private);
    }
    // What about admins? We ignoring them? Note we're talking about *real* admins who implicitly have every permission, not any homebrew
    if (!empty($modSettings['shd_admins_not_assignable'])) {
        $query = $smcFunc['db_query']('', '
			SELECT id_member
			FROM {db_prefix}members
			WHERE id_group = 1
				OR FIND_IN_SET(1, additional_groups)', array());
        $admins = array();
        while ($row = $smcFunc['db_fetch_row']($query)) {
            $admins[] = $row[0];
        }
        $smcFunc['db_free_result']($query);
        $staff = array_diff($staff, $admins);
    }
    // can they actually see said ticket
    $visible = shd_members_allowed_to('shd_view_ticket_any', $dept);
    if (empty($modSettings['shd_staff_ticket_self'])) {
        // by default, staff members can't be assigned a ticket if they started it
        $staff = array_diff($staff, array($ticket_owner));
    }
    // spit back the list of staff members who can see any ticket (+private if dealt with)
    return array_intersect($staff, $visible);
}
Example #2
0
function shd_setup_replies($first_msg)
{
    global $reply_request, $context, $smcFunc, $sourcedir, $modSettings, $settings;
    $context['ticket_form']['do_replies'] = false;
    $context['can_quote'] = false;
    $context['can_see_ip'] = shd_allowed_to('shd_staff', $context['ticket_form']['dept']);
    // OK, we're done with the ticket's own data. Now for replies.
    $context['get_replies'] = 'shd_prepare_reply_context';
    $query = shd_db_query('', '
		SELECT id_msg, id_member, modified_member
		FROM {db_prefix}helpdesk_ticket_replies
		WHERE id_ticket = {int:ticket}
			AND message_status = {int:msg_normal}
			AND id_msg > {int:first_msg}' . (empty($context['ticket_form']['msg']) || $context['ticket_form']['msg'] == $context['ticket_form']['first_msg'] ? '' : '
			AND id_msg < {int:reply_msg}') . '
		ORDER BY id_msg DESC
		LIMIT 10', array('ticket' => $context['ticket_id'], 'msg_normal' => MSG_STATUS_NORMAL, 'first_msg' => $first_msg, 'reply_msg' => !empty($context['ticket_form']['msg']) ? $context['ticket_form']['msg'] : 0));
    $messages = array();
    $posters = array();
    while ($row = $smcFunc['db_fetch_assoc']($query)) {
        if (!empty($row['id_member'])) {
            $posters[] = $row['id_member'];
        }
        if (!empty($row['modified_member'])) {
            $posters[] = $row['modified_member'];
        }
        $messages[] = $row['id_msg'];
    }
    $smcFunc['db_free_result']($query);
    $posters = array_unique($posters);
    $context['shd_is_staff'] = array();
    if (!empty($messages)) {
        $context['ticket_form']['do_replies'] = true;
        // Get the poster data
        if (!empty($posters)) {
            loadMemberData($posters);
            // Are they current team members?
            $team = array_intersect($posters, shd_members_allowed_to('shd_staff', $context['ticket_form']['dept']));
            foreach ($team as $member) {
                $context['shd_is_staff'][$member] = true;
            }
        }
        $reply_request = shd_db_query('', '
			SELECT
				id_msg, poster_time, poster_ip, id_member, modified_time, modified_name, modified_member, body,
				smileys_enabled, poster_name, poster_email
			FROM {db_prefix}helpdesk_ticket_replies
			WHERE id_msg IN ({array_int:message_list})
			ORDER BY id_msg DESC', array('message_list' => $messages));
        // Can we have a quote button in the replies? If so, we also need the relevant JS instantiation
        if (!empty($modSettings['shd_allow_ticket_bbc'])) {
            $context['can_quote'] = true;
            $context['html_headers'] .= '
			<script type="text/javascript"><!-- // --><![CDATA[
			var oQuickReply = new QuickReply({
				bDefaultCollapsed: false,
				iTicketId: ' . $context['ticket_id'] . ',
				iStart: ' . $context['start'] . ',
				sScriptUrl: smf_scripturl,
				sImagesUrl: "' . $settings['images_url'] . '",
				sContainerId: "quickReplyOptions",
				sImageId: "quickReplyExpand",
				sImageCollapsed: "collapse.png",
				sImageExpanded: "expand.png",
				sJumpAnchor: "quickreply",
				sHeaderId: "quickreplyheader",
				sFooterId: "quickreplyfooter"
			});
			// ]' . ']></script>';
        }
    } else {
        $reply_request = false;
        $context['first_message'] = 0;
        $context['first_new_message'] = false;
    }
}
/**
 *	Load the user preferences for the given user.
 *
 *	@param mixed $user Normally, an int being the user id of the user whose preferences should be attempted to be loaded. If === false, return the list of default prefs (for the pref UI), or if 0 or omitted, load the current user.
 *
 *	@return array If $user === false, the list of options, their types and default values is returned. Otherwise, return an array of prefs (adjusted for this user)
 *	@since 2.0
*/
function shd_load_user_prefs($user = 0)
{
    global $modSettings, $smcFunc, $user_info, $txt, $sourcedir;
    static $pref_groups = null, $base_prefs = null;
    if ($pref_groups === null) {
        $pref_groups = array('display' => array('icon' => 'preferences.png', 'enabled' => true), 'notify' => array('icon' => 'email.png', 'enabled' => true, 'check_all' => true), 'blocks' => array('icon' => 'log.png', 'enabled' => true), 'block_order' => array('icon' => 'move_down.png', 'enabled' => true));
        $base_prefs = array('display_unread_type' => array('options' => array('none' => 'shd_pref_display_unread_none', 'unread' => 'shd_pref_display_unread_unread', 'outstanding' => 'shd_pref_display_unread_outstanding'), 'default' => 'outstanding', 'type' => 'select', 'icon' => 'unread.png', 'group' => 'display', 'permission' => 'shd_staff', 'show' => empty($modSettings['shd_helpdesk_only']) && empty($modSettings['shd_disable_unread'])), 'display_order' => array('options' => array('asc' => 'shd_pref_display_order_asc', 'desc' => 'shd_pref_display_order_desc'), 'default' => 'asc', 'type' => 'select', 'icon' => 'move_down.png', 'group' => 'display', 'permission' => 'access_helpdesk', 'show' => true), 'blocks_assigned_count' => array('default' => 10, 'type' => 'int', 'icon' => 'assign.png', 'group' => 'blocks', 'permission' => 'shd_staff', 'show' => true), 'blocks_new_count' => array('default' => 10, 'type' => 'int', 'icon' => 'status.png', 'group' => 'blocks', 'permission' => 'access_helpdesk', 'show' => true), 'blocks_staff_count' => array('default' => 10, 'type' => 'int', 'icon' => 'staff.png', 'group' => 'blocks', 'permission' => 'access_helpdesk', 'show' => true), 'blocks_user_count' => array('default' => 10, 'type' => 'int', 'icon' => 'user.png', 'group' => 'blocks', 'permission' => 'access_helpdesk', 'show' => true), 'blocks_closed_count' => array('default' => 10, 'type' => 'int', 'icon' => 'resolved.png', 'group' => 'blocks', 'permission' => array('shd_view_closed_own', 'shd_view_closed_any'), 'show' => true), 'blocks_recycle_count' => array('default' => 10, 'type' => 'int', 'icon' => 'recycle.png', 'group' => 'blocks', 'permission' => 'shd_access_recyclebin', 'show' => true), 'blocks_withdeleted_count' => array('default' => 10, 'type' => 'int', 'icon' => 'recycle.png', 'group' => 'blocks', 'permission' => 'shd_access_recyclebin', 'show' => true), 'notify_new_ticket' => array('default' => 0, 'type' => 'check', 'icon' => 'log_newticket.png', 'group' => 'notify', 'permission' => 'shd_staff', 'show' => !empty($modSettings['shd_notify_new_ticket'])), 'notify_new_reply_own' => array('default' => 1, 'type' => 'check', 'icon' => 'log_newreply.png', 'group' => 'notify', 'permission' => 'shd_new_ticket', 'show' => !empty($modSettings['shd_notify_new_reply_own'])), 'notify_new_reply_assigned' => array('default' => 0, 'type' => 'check', 'icon' => 'log_assign.png', 'group' => 'notify', 'permission' => 'shd_staff', 'show' => !empty($modSettings['shd_notify_new_reply_assigned'])), 'notify_new_reply_previous' => array('default' => 0, 'type' => 'check', 'icon' => 'log_newreply.png', 'group' => 'notify', 'permission' => 'shd_staff', 'show' => !empty($modSettings['shd_notify_new_reply_previous'])), 'notify_new_reply_any' => array('default' => 0, 'type' => 'check', 'icon' => 'log_newreply.png', 'group' => 'notify', 'permission' => 'shd_staff', 'show' => !empty($modSettings['shd_notify_new_reply_any'])), 'notify_assign_me' => array('default' => 0, 'type' => 'check', 'icon' => 'assign.png', 'group' => 'notify', 'permission' => 'shd_staff', 'show' => !empty($modSettings['shd_notify_assign_me'])), 'notify_assign_own' => array('default' => 0, 'type' => 'check', 'icon' => 'assign.png', 'group' => 'notify', 'permission' => 'shd_new_ticket', 'show' => !empty($modSettings['shd_notify_assign_own'])), 'block_order_assigned_block' => array('default' => 'updated_asc', 'type' => 'select', 'icon' => 'assign.png', 'group' => 'block_order', 'permission' => 'shd_staff', 'show' => true), 'block_order_new_block' => array('default' => 'updated_asc', 'type' => 'select', 'icon' => 'status.png', 'group' => 'block_order', 'permission' => 'access_helpdesk', 'show' => true), 'block_order_staff_block' => array('default' => 'updated_asc', 'type' => 'select', 'icon' => 'staff.png', 'group' => 'block_order', 'permission' => 'access_helpdesk', 'show' => true), 'block_order_user_block' => array('default' => 'updated_asc', 'type' => 'select', 'icon' => 'user.png', 'group' => 'block_order', 'permission' => 'access_helpdesk', 'show' => true), 'block_order_closed_block' => array('default' => 'updated_desc', 'type' => 'select', 'icon' => 'resolved.png', 'group' => 'block_order', 'permission' => array('shd_view_closed_own', 'shd_view_closed_any'), 'show' => true), 'block_order_recycle_block' => array('default' => 'updated_desc', 'type' => 'select', 'icon' => 'recycle.png', 'group' => 'block_order', 'permission' => 'shd_access_recyclebin', 'show' => true), 'block_order_withdeleted_block' => array('default' => 'updated_desc', 'type' => 'select', 'icon' => 'recycle.png', 'group' => 'block_order', 'permission' => 'shd_access_recyclebin', 'show' => true));
        // We want to add the preferences per block. Because we already know what options there are per block elsewhere, let's reuse that.
        if (!function_exists('shd_get_block_columns')) {
            require_once $sourcedir . '/sd_source/SimpleDesk.php';
        }
        $blocks = array('assigned', 'new', 'staff', shd_allowed_to('shd_staff', 0) ? 'user_staff' : 'user_user', 'closed', 'recycled', 'withdeleted');
        foreach ($blocks as $block) {
            $items = shd_get_block_columns($block);
            if (empty($items)) {
                continue;
            }
            if ($block == 'user_staff' || $block == 'user_user') {
                $block = 'user';
            } elseif ($block == 'recycled') {
                $block = 'recycle';
            }
            $block_id = 'block_order_' . $block . '_block';
            $base_prefs[$block_id]['options'] = array();
            foreach ($items as $item) {
                if ($item != 'actions') {
                    $item = str_replace(array('_', 'startinguser'), array('', 'starter'), $item);
                    $base_prefs[$block_id]['options'][$item . '_asc'] = 'shd_pref_block_order_' . $item . '_asc';
                    $base_prefs[$block_id]['options'][$item . '_desc'] = 'shd_pref_block_order_' . $item . '_desc';
                }
            }
        }
        // Now engage any hooks.
        call_integration_hook('shd_hook_prefs', array(&$pref_groups, &$base_prefs));
        foreach ($base_prefs as $pref => $details) {
            if (empty($pref_groups[$details['group']]['enabled']) || empty($details['show'])) {
                unset($base_prefs[$pref]);
            }
        }
    }
    // Do we just want the prefs list?
    if ($user === false) {
        return array('groups' => $pref_groups, 'prefs' => $base_prefs);
    }
    $prefs = array();
    if ($user == 0 || $user == $user_info['id']) {
        $user = $user_info['id'];
        // Start with the defaults, but dealing with permissions as we go
        foreach ($base_prefs as $pref => $details) {
            if (empty($details['permission']) || shd_allowed_to($details['permission'], 0)) {
                $prefs[$pref] = $details['default'];
            }
        }
    } else {
        foreach ($base_prefs as $pref => $details) {
            if (empty($details['permission'])) {
                continue;
            }
            if (is_array($details['permission'])) {
                foreach ($details['permission'] as $perm) {
                    if (in_array($user, shd_members_allowed_to($perm))) {
                        $prefs[$pref] = $details['default'];
                        break;
                    }
                }
            } else {
                if (in_array($user, shd_members_allowed_to($details['permission']))) {
                    $prefs[$pref] = $details['default'];
                }
            }
        }
    }
    // Now, the database
    $query = $smcFunc['db_query']('', '
		SELECT variable, value
		FROM {db_prefix}helpdesk_preferences
		WHERE id_member = {int:user}', array('user' => (int) $user));
    while ($row = $smcFunc['db_fetch_assoc']($query)) {
        if (isset($prefs[$row['variable']])) {
            $prefs[$row['variable']] = $row['value'];
        }
    }
    return $prefs;
}
/**
 *	Returns the list of possible assignees for a ticket for AJAX assignment purposes.
 *
 *	Operations:
 *	- Session check
 * 	- Permissions check (that you can assign a ticket to someone else); if you can't assign a ticket to someone else, bail.
 *	- Get the list of information for a ticket (which implicitly checks ticket access); if you can't see the ticket, bail.
 *	- Get the list of who can be assigned a ticket.
 *	- Return that via AJAX.
*/
function shd_ajax_assign()
{
    global $context, $smcFunc, $txt, $sourcedir, $user_profile;
    checkSession('get');
    if (!empty($context['ticket_id'])) {
        $query = shd_db_query('', '
			SELECT hdt.private, hdt.id_member_started, id_member_assigned, id_dept, hdt.status, 1 AS valid
			FROM {db_prefix}helpdesk_tickets AS hdt
			WHERE {query_see_ticket}
				AND hdt.id_ticket = {int:ticket}', array('ticket' => $context['ticket_id']));
        if ($smcFunc['db_num_rows']($query) != 0) {
            list($private, $ticket_starter, $ticket_assigned, $dept, $status, $valid) = $smcFunc['db_fetch_row']($query);
        }
        $smcFunc['db_free_result']($query);
    }
    if (empty($valid)) {
        return $context['ajax_return'] = array('error' => $txt['shd_no_ticket']);
    }
    require_once $sourcedir . '/sd_source/SimpleDesk-Assign.php';
    $assignees = shd_get_possible_assignees($private, $ticket_starter, $dept);
    array_unshift($assignees, 0);
    // add the unassigned option in at the start
    if (empty($assignees)) {
        return $context['ajax_return'] = array('error' => $txt['shd_no_staff_assign']);
    }
    if (!shd_allowed_to('shd_assign_ticket_any', $dept) || $status == TICKET_STATUS_CLOSED || $status == TICKET_STATUS_DELETED) {
        return $context['ajax_return'] = array('error' => $txt['shd_cannot_assign']);
    }
    // OK, so we have the general values we need. Let's get user names, and get ready to kick this back to the user. We'll build the XML here though.
    loadMemberData($assignees);
    // Just out of interest, who's an admin?
    $admins = shd_members_allowed_to('admin_helpdesk', $dept);
    $context['ajax_raw'] = '<response>';
    foreach ($assignees as $assignee) {
        $context['ajax_raw'] .= '
<member uid="' . $assignee . '"' . (!empty($assignee) ? in_array($assignee, $admins) ? ' admin="yes"' : ' admin="no"' : '') . ($ticket_assigned == $assignee ? ' assigned="yes"' : '') . '><![CD' . 'ATA[' . (empty($assignee) ? '<span class="error">' . $txt['shd_unassigned'] . '</span>' : $user_profile[$assignee]['member_name']) . ']' . ']></member>';
    }
    $context['ajax_raw'] .= '
</response>';
}
Example #5
0
/**
 *	Gets a list of all staff members within the helpdesk.
 *
 *	@param boolean $honour_admin_setting Within the administration panel is the option to exclude forum admins from being considered staff (so can't assign tickets to them). If true (default), assume the outcome of that should be applied here too.
 *	@param string $output_method Leave as default or explicitly set to 'echo' for this function to output a list of helpdesk staff members, set to 'array' to block output, and have the standard contents back.
 *	@return array The return is always an array of members that are staff; contains many details about members since SMF's member context is loaded (including avatar, personal text and so on)
 *	@since 2.0
*/
function ssi_staffMembers($honour_admin_setting = true, $output_method = 'echo')
{
    global $modSettings, $smcFunc, $memberContext;
    $staff = shd_members_allowed_to('shd_staff');
    if ($honour_admin_setting && !empty($modSettings['shd_admins_not_assignable'])) {
        $admins = array();
        $query = $smcFunc['db_query']('', '
			SELECT id_member
			FROM {db_prefix}members
			WHERE id_group = {int:id_group}
				OR FIND_IN_SET({int:id_group}, additional_groups)', array('id_group' => 1));
        while ($row = $smcFunc['db_fetch_row']($query)) {
            $admins[] = $row[0];
        }
        $staff = array_diff($staff, $admins);
    }
    if (empty($staff)) {
        return array();
    }
    loadMemberData($staff);
    if ($output_method == 'echo') {
        echo '
		<table border="0" class="ssi_table">';
    }
    $query_members = array();
    foreach ($staff as $member) {
        // Load their context data.
        if (!loadMemberContext($member)) {
            continue;
        }
        // Store this member's information.
        $query_members[$member] = $memberContext[$member];
        // Only do something if we're echo'ing.
        if ($output_method == 'echo') {
            echo '
			<tr>
				<td align="right" valign="top" nowrap="nowrap">
					', $query_members[$member]['link'], '
					<br />', $query_members[$member]['blurb'], '
					<br />', $query_members[$member]['avatar']['image'], '
				</td>
			</tr>';
        }
    }
    // End the table if appropriate.
    if ($output_method == 'echo') {
        echo '
		</table>';
    }
    // Send back the data.
    return $query_members;
}
/**
 *	Returns a list of all the possible people that would want notification, that can see the ticket we're interested in.
 *
 *	@param int $dept The department the given ticket is in.
 *	@param bool $private Whether the given ticket is private or not.
 *	@param int $ticket_starter User id of the ticket starter.
 *	@param bool $exclude_admin If true, exclude forum admins from the list of possible candidates.
 *	@return array An array of user ids of the staff members (and ticket starter) that can see tickets, matching the given criteria of department, privacy and permissions.
*/
function shd_get_visible_list($dept, $private, $ticket_starter = 0, $include_admin = true, $include_current_user = false)
{
    global $smcFunc, $context;
    // So, the list of possible people will include the staff list and the ticket starter if provided.
    $people = shd_members_allowed_to('shd_staff', $dept);
    if (!empty($ticket_starter)) {
        $people[] = $ticket_starter;
    }
    // Firstly, figure out who can see tickets that aren't their own. (Or that they can see their own and this is their ticket)
    $see_tickets = shd_members_allowed_to('shd_view_ticket_any', $dept);
    if (!empty($ticket_starter) && !in_array($ticket_starter, $see_tickets)) {
        $see_own = shd_members_allowed_to('shd_view_ticket_own', $dept);
        if (in_array($ticket_starter, $see_own)) {
            $see_tickets[] = $ticket_starter;
        }
    }
    $people = array_intersect($people, $see_tickets);
    // If the ticket is private, we need to figure out who can see it. This is tricky.
    if ($private) {
        $private = array_merge(shd_members_allowed_to('shd_view_ticket_private_any', $dept), shd_members_allowed_to('shd_alter_privacy_any', $dept));
        // That covers for those who can see any, what about those who can see own (but not any)?
        if (!empty($ticket_starter) && !in_array($ticket_starter, $private)) {
            $own_private = array_merge(shd_members_allowed_to('shd_view_ticket_private_own', $dept), shd_members_allowed_to('shd_alter_privacy_own', $dept));
            if (in_array($ticket_starter, $own_private)) {
                $private[] = $ticket_starter;
            }
        }
        $people = array_intersect($people, $private);
    }
    if (!$include_admin) {
        $query = $smcFunc['db_query']('', '
			SELECT id_member
			FROM {db_prefix}members
			WHERE id_group = 1
				OR FIND_IN_SET(1, additional_groups)', array());
        $context['list_admin_exclude'] = array();
        while ($row = $smcFunc['db_fetch_row']($query)) {
            $context['list_admin_exclude'][] = $row[0];
        }
        $smcFunc['db_free_result']($query);
        $people = array_diff($people, $context['list_admin_exclude']);
    }
    if (!$include_current_user) {
        $people = array_diff($people, array($context['user']['id']));
    }
    return $people;
}
Example #7
0
/**
 *	Loads all the data and sets all the options for displaying a ticket.
 *
 *	This function does a lot of work in setting up a ticket to be displayed:
 *	<ul>
 *		<li>Invokes shd_load_ticket() to get the principle data</li>
 *		<li>Creates $context['ticket'] to hold the data block, some of which is derived from the return of shd_load_ticket():
 *			<ul>
 *				<li>id: regular numeric ticket id</li>
 *				<li>display_id: zero padded ticket id (e.g. 00001)</li>
 *				<li>subject: censored version of the subject</li>
 *				<li>first_msg: id of the opening post that forms the ticket body</li>
 *				<li>body: formatted (parsed for smileys and bbcode) version of the ticket post</li>
 *				<li>id_member: user id of the ticket's poster</li>
 *				<li>id_member_assigned: user id of the ticket's assigned user</li>
 *				<li>member: hash array of the ticket poster's details:
 *					<ul>
 *						<li>id: their user id</li>
 *						<li>name: the name stated in the ticket post for that use</li>
 *						<li>link: link to the profile of the user</li>
 *					</ul>
 *				</li>
 *				<li>assigned: hash array of the assignee of the ticket:
 *					<ul>
 *						<li>id: their user id</li>
 *						<li>name: name of the assignee, or 'Unassigned'</li>
 *						<li>link: a full HTML link to their profile, or 'Unassigned' in red text</li>
 *					</ul>
 *				</li>
 *				<li>assigned_self: boolean, whether the ticket is assigned to the current user or not</li>
 *				<li>ticket_opener: boolean, whether the current user is the user who opened this ticket</li>
 *				<li>urgency: hash array
 *					<ul>
 *						<li>level: numeric identifier of current ticket urgency</li>
 *						<li>label: the HTML label of the urgency, including being in red for "Very High" or above</li>
 *						<li>increase: Boolean, whether the current ticket urgency can be increased given the current ticket state and user permissions</li>
 *						<li>decrease: Boolean, whether the current ticket urgency can be increased given the current ticket state and user permissions</li>
 *					</ul>
 *				</li>
 *				<li>status: hash array
 *					<ul>
 *						<li>level: numeric, current status identifier</li>
 *						<li>label: string representing the current status</li>
 *					</ul>
 *				<li>num_replies: the number of replies to the ticket so far</li>
 *				<li>deleted_replies: how many deleted replies in this ticket</li>
 *				<li>poster_time: formatted string containing the time the ticket was opened</li>
 *				<li>privacy: hash array
 *					<ul>
 *						<li>label: current label to be used with the privacy item</li>
 *						<li>can_change: Boolean, whether the user's permission with this ticket allows us to edit the ticket privacy</li>
 *					</ul>
 *				</li>
 *				<li>closed: Boolean, represents whether this ticket is closed (used a lot with the navigation menu)</li>
 *				<li>deleted: Boolean, represents whether this ticket is deleted (used a lot with the navigation menu)</li>
 *				<li>ip_address: IP address logged at the time the ticket was opened; if moderate_forum_members permission is available, this will be a link to the track IP area</li>
 *				<li>modified: if the ticket has been modified, also get the modified details:
 *					<ul>
 *						<li>id: user id who edited the ticket (not always available)</li>
 *						<li>time: formatted string of the time the post was edited</li>
 *						<li>timestamp: raw timestamp of the time the post was edited</li>
 *						<li>name: user name of the editing user; if we have a definite user id, this should contain the current name, falling back to the previously stored name</li>
 *						<li>link: if we have a known, valid user id for the post's editor, this will contain a link to their profile, with the link text using their current display name; alternatively it will contain a regular string which is the username stored with the edit.</li>
 *					</ul>
 *				</li>
 *				<li>display_recycle: Either holds the $txt identifier of the message to apply as a warning, or false if displaying of recycling stuff in this ticket isn't appropriate (either for permissions or just because of no deleted replies, or we're just in regular ticket view)</li>
 *			</ul>
 *		</li>
 *		<li>define the page index with SMF's constructPageIndex</li>
 *		<li>query for all the ids of messages we might display, followed by querying for the message details themselves, pushing that query resource to $reply_request so we can use it in shd_view_replies() later</li>
 *		<li>load details of all the users applicable for posts in this page</li>
 *		<li>request all the visible attachments from {@link shd_display_load_attachments()}</li>
 *		<li>since we are viewing this ticket, mark it read</li>
 *		<li>set up the breadcrumb trail</li>
 *		<li>set up the ticket navigation menu</li>
 *		<li>call in the editor component from SimpleDesk-Post.php and friends, ready for Quick Reply</li>
 *		<li>invoke the different Javascript objects that are applicable on the page:
 *			<ul>
 *				<li>privacy changer</li>
 *				<li>urgency changer</li>
 *				<li>quick reply / quote / go advanced</li>
 *			</ul>
 *		</li>
 *	</ul>
 *
 *	@see shd_prepare_ticket_context()
 *	@since 1.0
*/
function shd_view_ticket()
{
    global $context, $txt, $scripturl, $settings, $reply_request, $smcFunc, $modSettings, $memberContext, $sourcedir, $user_info, $options;
    loadTemplate('sd_template/SimpleDesk-Display');
    $context['template_layers'][] = 'shd_display_nojs';
    $ticketinfo = shd_load_ticket();
    // How much are we sticking on each page?
    $context['messages_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) && !WIRELESS ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
    censorText($ticketinfo['subject']);
    censorText($ticketinfo['body']);
    $context['user_list'] = array();
    // as we go along, build a list of users who are relevant
    $context['ticket'] = array('id' => $context['ticket_id'], 'dept' => $ticketinfo['dept'], 'dept_name' => $ticketinfo['dept_name'], 'display_id' => str_pad($context['ticket_id'], $modSettings['shd_zerofill'], '0', STR_PAD_LEFT), 'subject' => $ticketinfo['subject'], 'first_msg' => $ticketinfo['id_first_msg'], 'body' => shd_format_text($ticketinfo['body'], $ticketinfo['smileys_enabled'], 'shd_reply_' . $ticketinfo['id_first_msg']), 'id_member' => $ticketinfo['id_member'], 'id_member_assigned' => $ticketinfo['assigned_id'], 'member' => array('id' => $ticketinfo['starter_id'], 'name' => $ticketinfo['starter_name'], 'link' => shd_profile_link($ticketinfo['starter_name'], $ticketinfo['starter_id'])), 'assigned' => array('id' => $ticketinfo['assigned_id'], 'name' => $ticketinfo['assigned_id'] > 0 ? $ticketinfo['assigned_name'] : $txt['shd_unassigned'], 'link' => $ticketinfo['assigned_id'] > 0 ? shd_profile_link($ticketinfo['assigned_name'], $ticketinfo['assigned_id']) : '<span class="error">' . $txt['shd_unassigned'] . '</span>'), 'assigned_self' => $ticketinfo['assigned_id'] == $user_info['id'], 'ticket_opener' => $ticketinfo['starter_id'] == $user_info['id'], 'urgency' => array('level' => $ticketinfo['urgency'], 'label' => $ticketinfo['urgency'] > TICKET_URGENCY_HIGH ? '<span class="error">' . $txt['shd_urgency_' . $ticketinfo['urgency']] . '</span>' : $txt['shd_urgency_' . $ticketinfo['urgency']]), 'status' => array('level' => $ticketinfo['status'], 'label' => $txt['shd_status_' . $ticketinfo['status']]), 'num_replies' => $ticketinfo['num_replies'], 'deleted_replies' => $ticketinfo['deleted_replies'], 'poster_time' => timeformat($ticketinfo['poster_time']), 'privacy' => array('label' => $ticketinfo['private'] ? $txt['shd_ticket_private'] : $txt['shd_ticket_notprivate'], 'can_change' => shd_allowed_to('shd_alter_privacy_any', $ticketinfo['dept']) || shd_allowed_to('shd_alter_privacy_own', $ticketinfo['dept']) && $ticketinfo['id_member'] == $user_info['id']), 'closed' => $ticketinfo['closed'], 'deleted' => $ticketinfo['deleted']);
    // Fix the departmental link since we know we're inside a department now.
    if ($context['shd_multi_dept']) {
        $context['shd_department'] = $context['ticket']['dept'];
        $context['shd_dept_link'] = ';dept=' . $context['ticket']['dept'];
    }
    // IP address next
    $context['link_ip_address'] = allowedTo('moderate_forum');
    // for trackip access
    if (shd_allowed_to('shd_view_ip_any', $context['ticket']['dept']) || $context['ticket']['ticket_opener'] && shd_allowed_to('shd_view_ip_own', $context['ticket']['dept'])) {
        $context['ticket']['ip_address'] = $context['link_ip_address'] ? '<a href="' . $scripturl . '?action=trackip;searchip=' . $ticketinfo['starter_ip'] . '">' . $ticketinfo['starter_ip'] . '</a>' : $ticketinfo['starter_ip'];
    }
    // Stuff concerning whether the ticket is deleted or not
    // Display recycling stuff if: ticket is deleted (if we can see it, we can see the bin) OR ticket has deleted replies and we can see the bin and we requested to see them
    $context['ticket']['display_recycle_replies'] = true;
    if ($context['ticket']['deleted']) {
        $context['ticket']['display_recycle'] = $txt['shd_ticket_has_been_deleted'];
    } elseif ($context['ticket']['deleted_replies'] > 0) {
        if (shd_allowed_to('shd_access_recyclebin', $context['ticket']['dept'])) {
            $context['ticket']['display_recycle'] = $txt['shd_ticket_replies_deleted'];
            $ticketlink = $scripturl . '?action=helpdesk;sa=ticket;ticket=' . $context['ticket_id'] . (isset($_REQUEST['recycle']) ? '' : ';recycle');
            $context['ticket']['display_recycle'] .= ' ' . sprintf(isset($_REQUEST['recycle']) ? $txt['shd_ticket_replies_deleted_view'] : $txt['shd_ticket_replies_deleted_link'], $ticketlink);
            $context['ticket']['display_recycle_replies'] = isset($_REQUEST['recycle']);
        } else {
            $context['ticket']['display_recycle_replies'] = false;
        }
    } else {
        $context['ticket']['display_recycle'] = false;
        $context['ticket']['display_recycle_replies'] = false;
    }
    // Ticket privacy
    $context['ticket']['privacy']['can_change'] = $context['ticket']['privacy']['can_change'] && (!$context['ticket']['closed'] && !$context['ticket']['deleted']);
    if (empty($modSettings['shd_privacy_display']) || $modSettings['shd_privacy_display'] == 'smart') {
        $context['display_private'] = shd_allowed_to('shd_view_ticket_private_any', $context['ticket']['dept']) || shd_allowed_to(array('shd_alter_privacy_own', 'shd_alter_privacy_any'), $context['ticket']['dept']) || $ticketinfo['private'];
    } else {
        $context['display_private'] = true;
    }
    if ($ticketinfo['modified_time'] > 0) {
        $context['ticket']['modified'] = array('id' => $ticketinfo['modified_id'], 'name' => $ticketinfo['modified_name'], 'link' => shd_profile_link($ticketinfo['modified_name'], $ticketinfo['modified_id']), 'timestamp' => $ticketinfo['modified_time'], 'time' => timeformat($ticketinfo['modified_time']));
    }
    $context['ticket']['urgency'] += shd_can_alter_urgency($ticketinfo['urgency'], $ticketinfo['starter_id'], $ticketinfo['closed'], $ticketinfo['deleted'], $context['ticket']['dept']);
    $context['total_visible_posts'] = empty($context['display_recycle']) ? $context['ticket']['num_replies'] : (int) $context['ticket']['num_replies'] + (int) $context['ticket']['deleted_replies'];
    // OK, before we go crazy, we might need to alter the ticket start. If we're in descending order (non default), we need to reverse it.
    if (!empty($context['shd_preferences']['display_order']) && $context['shd_preferences']['display_order'] == 'desc') {
        if (empty($context['ticket_start_natural'])) {
            $context['ticket_start_from'] = $context['total_visible_posts'] - (empty($context['ticket_start']) ? $context['total_visible_posts'] : $context['ticket_start']);
        } else {
            $context['ticket_start_from'] = $context['ticket_start'];
        }
        $context['ticket_sort'] = 'DESC';
    } else {
        $context['ticket_start_from'] = $context['ticket_start'];
        $context['ticket_sort'] = 'ASC';
    }
    $context['page_index'] = shd_no_expand_pageindex($scripturl . '?action=helpdesk;sa=ticket;ticket=' . $context['ticket_id'] . '.%1$d' . (isset($_REQUEST['recycle']) ? ';recycle' : '') . '#replies', $context['ticket_start_from'], $context['total_visible_posts'], $context['messages_per_page'], true);
    $context['get_replies'] = 'shd_prepare_ticket_context';
    $query = shd_db_query('', '
		SELECT id_msg, id_member, modified_member
		FROM {db_prefix}helpdesk_ticket_replies
		WHERE id_ticket = {int:ticket}
			AND id_msg > {int:first_msg}' . (!empty($context['ticket']['display_recycle_replies']) ? '' : '
			AND message_status = {int:msg_status}') . '
		ORDER BY id_msg {raw:sort}' . ($context['messages_per_page'] == -1 ? '' : '
		LIMIT ' . $context['ticket_start_from'] . ', ' . $context['messages_per_page']), array('ticket' => $context['ticket_id'], 'first_msg' => $ticketinfo['id_first_msg'], 'msg_status' => MSG_STATUS_NORMAL, 'sort' => $context['ticket_sort']));
    $context['ticket_messages'] = array();
    $posters = array();
    while ($row = $smcFunc['db_fetch_assoc']($query)) {
        if (!empty($row['id_member'])) {
            $posters[] = $row['id_member'];
        }
        if (!empty($row['modified_member'])) {
            $posters[] = $row['modified_member'];
        }
        $context['ticket_messages'][] = $row['id_msg'];
    }
    $smcFunc['db_free_result']($query);
    // We might want the OP's avatar, add 'em to the list -- just in case.
    $posters[] = $context['ticket']['id_member'];
    $posters = array_unique($posters);
    $context['shd_is_staff'] = array();
    // Get the poster data
    if (!empty($posters)) {
        loadMemberData($posters);
        // Are they current team members?
        $team = array_intersect($posters, shd_members_allowed_to('shd_staff', $context['ticket']['dept']));
        foreach ($team as $member) {
            $context['shd_is_staff'][$member] = true;
        }
    }
    if (!empty($context['ticket_messages'])) {
        $reply_request = shd_db_query('', '
			SELECT
				id_msg, poster_time, poster_ip, id_member, modified_time, modified_name, modified_member, body,
				smileys_enabled, poster_name, poster_email, message_status
			FROM {db_prefix}helpdesk_ticket_replies
			WHERE id_msg IN ({array_int:message_list})' . (!empty($context['ticket']['display_recycle']) ? '' : '
				AND message_status IN ({array_int:msg_normal})') . '
			ORDER BY id_msg {raw:sort}', array('message_list' => $context['ticket_messages'], 'msg_normal' => array(MSG_STATUS_NORMAL), 'sort' => $context['ticket_sort']));
    } else {
        $reply_request = false;
        $context['first_message'] = 0;
        $context['first_new_message'] = false;
    }
    // Load all the custom fields
    // First, get all the values that could apply to the current context. We'll deal with what's active/inactive and where it all goes shortly.
    $query = shd_db_query('', '
		SELECT cfv.id_post, cfv.id_field, cfv.value, cfv.post_type
		FROM {db_prefix}helpdesk_custom_fields_values AS cfv
		WHERE (cfv.id_post = {int:ticket} AND cfv.post_type = 1)' . (!empty($context['ticket_messages']) ? '
			OR (cfv.id_post IN ({array_int:msgs}) AND cfv.post_type = 2)' : ''), array('ticket' => $context['ticket_id'], 'msgs' => $context['ticket_messages']));
    $field_values = array();
    while ($row = $smcFunc['db_fetch_assoc']($query)) {
        $field_values[$row['post_type'] == CFIELD_TICKET ? 'ticket' : $row['id_post']][$row['id_field']] = $row;
    }
    $smcFunc['db_free_result']($query);
    // Set up the storage.
    $context['custom_fields_replies'] = array();
    $context['ticket']['custom_fields'] = array('details' => array(), 'information' => array(), 'prefix' => array(), 'prefixfilter' => array());
    $context['ticket_form']['custom_fields_context'] = 'reply';
    $context['ticket_form']['custom_fields'] = array();
    $query = shd_db_query('', '
		SELECT cf.id_field, cf.active, cf.field_order, cf.field_name, cf.field_desc, cf.field_loc, cf.icon,
			cf.field_type, cf.default_value, cf.bbc, cf.can_see, cf.can_edit, cf.field_length,
			cf.field_options, cf.display_empty, cfd.required, cf.placement
		FROM {db_prefix}helpdesk_custom_fields AS cf
			INNER JOIN {db_prefix}helpdesk_custom_fields_depts AS cfd ON (cf.id_field = cfd.id_field AND cfd.id_dept = {int:dept})
		WHERE cf.active = 1
		ORDER BY cf.field_order', array('dept' => $context['ticket']['dept']));
    // Loop through all fields and figure out where they should be.
    $is_staff = shd_allowed_to('shd_staff', $context['ticket']['dept']);
    $is_admin = shd_allowed_to('admin_helpdesk', $context['ticket']['dept']);
    // this includes forum admins
    $placements = array(CFIELD_PLACE_DETAILS => 'details', CFIELD_PLACE_INFO => 'information', CFIELD_PLACE_PREFIX => 'prefix', CFIELD_PLACE_PREFIXFILTER => 'prefixfilter');
    while ($row = $smcFunc['db_fetch_assoc']($query)) {
        list($user_see, $staff_see) = explode(',', $row['can_see']);
        list($user_edit, $staff_edit) = explode(',', $row['can_edit']);
        if ($is_admin) {
            $editable = true;
        } elseif ($is_staff) {
            if ($staff_see == 0) {
                continue;
            }
            $editable = $staff_edit == 1;
        } elseif ($user_see == 1) {
            $editable = $user_edit == 1;
        } else {
            continue;
        }
        // If this is going to be displayed for the individual ticket, we need to figure out where it should go.
        if ($row['field_loc'] & CFIELD_TICKET) {
            $pos = $placements[$row['placement']];
        }
        $field = array('id' => $row['id_field'], 'name' => $row['field_name'], 'desc' => parse_bbc($row['field_desc'], false), 'icon' => $row['icon'], 'type' => $row['field_type'], 'default_value' => $row['field_type'] == CFIELD_TYPE_LARGETEXT ? explode(',', $row['default_value']) : $row['default_value'], 'options' => !empty($row['field_options']) ? unserialize($row['field_options']) : array(), 'display_empty' => !empty($row['required']) ? true : !empty($row['display_empty']), 'bbc' => !empty($row['bbc']) && ($row['field_type'] == CFIELD_TYPE_TEXT || $row['field_type'] == CFIELD_TYPE_LARGETEXT) && $row['placement'] != CFIELD_PLACE_PREFIX, 'editable' => !empty($editable));
        if (!empty($field['options']) && empty($field['options']['inactive'])) {
            $field['options']['inactive'] = array();
        }
        if (in_array($field['type'], array(CFIELD_TYPE_RADIO, CFIELD_TYPE_SELECT, CFIELD_TYPE_MULTI))) {
            foreach ($field['options'] as $k => $v) {
                if ($k != 'inactive' && strpos($v, '[') !== false) {
                    $field['options'][$k] = parse_bbc($v, false);
                }
            }
        }
        if ($row['field_loc'] & CFIELD_REPLY && $field['editable']) {
            $context['ticket_form']['custom_fields']['reply'][$field['id']] = $field;
        }
        // Add fields to the master list, getting any values as we go.
        if ($row['field_loc'] & CFIELD_TICKET && (!empty($field_values['ticket'][$row['id_field']]['post_type']) && $field_values['ticket'][$row['id_field']]['post_type'] == CFIELD_TICKET || $field['display_empty'])) {
            if (isset($field_values['ticket'][$row['id_field']])) {
                $field['value'] = $field['bbc'] ? shd_format_text($field_values['ticket'][$row['id_field']]['value']) : $field_values['ticket'][$row['id_field']]['value'];
            }
            $context['ticket']['custom_fields'][$pos][$row['id_field']] = $field;
        }
        if ($row['field_loc'] & CFIELD_REPLY) {
            foreach ($field_values as $dest => $field_details) {
                unset($field['value']);
                if ($dest == 'ticket' || !isset($field_details[$row['id_field']]) || $field_details[$row['id_field']]['post_type'] != CFIELD_REPLY) {
                    continue;
                }
                $field['value'] = $field['bbc'] ? shd_format_text($field_details[$row['id_field']]['value']) : $field_details[$row['id_field']]['value'];
                $context['custom_fields_replies'][$dest][$row['id_field']] = $field;
            }
            // We also need to attach the field to replies didn't get the field added, in the event that the field should be displayed by default.
            if ($field['display_empty']) {
                foreach ($context['ticket_messages'] as $msg) {
                    if (!isset($context['custom_fields_replies'][$msg][$row['id_field']])) {
                        $field['value'] = '';
                        $context['custom_fields_replies'][$msg][$row['id_field']] = $field;
                    }
                }
            }
        }
    }
    $smcFunc['db_free_result']($query);
    // Grab the avatar for the poster
    $context['ticket']['poster_avatar'] = empty($context['ticket']['member']['id']) ? array() : (loadMemberContext($context['ticket']['id_member']) ? $memberContext[$context['ticket']['id_member']]['avatar'] : array());
    // Before we grab attachments, also make sure we get any from the first msg (i.e. the ticket)
    $context['ticket_messages'][] = $context['ticket']['first_msg'];
    shd_display_load_attachments();
    // Mark read goes here
    if (!empty($user_info['id'])) {
        $smcFunc['db_insert']('replace', '{db_prefix}helpdesk_log_read', array('id_ticket' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), array($context['ticket_id'], $user_info['id'], $ticketinfo['id_last_msg']), array('id_member', 'id_topic'));
    }
    // Template stuff
    $context['sub_template'] = 'viewticket';
    $ticketname = '';
    if (!empty($context['ticket']['custom_fields']['prefix'])) {
        $ticketname = '[' . $context['ticket']['display_id'] . '] ';
        $fields = '';
        foreach ($context['ticket']['custom_fields']['prefix'] as $field) {
            if (empty($field['value'])) {
                continue;
            }
            if ($field['type'] == CFIELD_TYPE_CHECKBOX) {
                $fields .= !empty($field['value']) ? $txt['yes'] . ' ' : $txt['no'] . ' ';
            } elseif ($field['type'] == CFIELD_TYPE_SELECT || $field['type'] == CFIELD_TYPE_RADIO) {
                $fields .= trim(strip_tags($field['options'][$field['value']])) . ' ';
            } elseif ($field['type'] == CFIELD_TYPE_MULTI) {
                $values = explode(',', $field['value']);
                foreach ($values as $value) {
                    $fields .= trim(strip_tags($field['options'][$value])) . ' ';
                }
            } else {
                $fields .= $field['value'] . ' ';
            }
        }
        $fields = trim($fields);
        $ticketname .= (!empty($fields) ? '[' . trim($fields) . '] ' : '') . $context['ticket']['subject'];
    } else {
        $ticketname = '[' . $context['ticket']['display_id'] . '] ' . $context['ticket']['subject'];
    }
    $context['page_title'] = $txt['shd_helpdesk'] . ' ' . $ticketname;
    // If we're in a department, display that.
    if ($context['shd_multi_dept']) {
        $context['linktree'][] = array('url' => $scripturl . '?' . $context['shd_home'] . $context['shd_dept_link'], 'name' => $context['ticket']['dept_name']);
    }
    // Build the link tree. If the ticket is recycled, display 'Recycle bin'.
    if ($context['ticket']['status']['level'] == TICKET_STATUS_DELETED) {
        $context['linktree'][] = array('url' => $scripturl . '?action=helpdesk;sa=recyclebin' . $context['shd_dept_link'], 'name' => $txt['shd_recycle_bin']);
    } elseif ($context['ticket']['status']['level'] == TICKET_STATUS_CLOSED) {
        $context['linktree'][] = array('url' => $scripturl . '?action=helpdesk;sa=closedtickets' . $context['shd_dept_link'], 'name' => $txt['shd_tickets_closed']);
    }
    // Lastly add the ticket name and link to the linktree.
    $context['linktree'][] = array('url' => $scripturl . '?action=helpdesk;sa=ticket;ticket=' . $context['ticket_id'], 'name' => $ticketname);
    // Ticket navigation / permission
    $context['can_move_dept'] = !empty($context['shd_multi_dept']) && (shd_allowed_to('shd_move_dept_any', $context['ticket']['dept']) || $context['ticket']['ticket_opener'] && shd_allowed_to('shd_move_dept_own', $context['ticket']['dept']));
    $context['can_reply'] = !$context['ticket']['closed'] && !$context['ticket']['deleted'] && (shd_allowed_to('shd_reply_ticket_any', $context['ticket']['dept']) || $context['ticket']['ticket_opener'] && shd_allowed_to('shd_reply_ticket_own', $context['ticket']['dept']));
    // needs perms - calc'd here because we use it in display template too
    $context['can_quote'] = $context['can_reply'] && !empty($modSettings['shd_allow_ticket_bbc']);
    $context['can_go_advanced'] = !empty($modSettings['shd_allow_ticket_bbc']) || !empty($modSettings['allow_ticket_smileys']) || shd_allowed_to('shd_post_attachment', $context['ticket']['dept']);
    $context['shd_can_move_to_topic'] = empty($modSettings['shd_disable_tickettotopic']) && shd_allowed_to('shd_ticket_to_topic', $context['ticket']['dept']) && empty($modSettings['shd_helpdesk_only']);
    $context['can_solve'] = !$context['ticket']['closed'] && !$context['ticket']['deleted'] && (shd_allowed_to('shd_resolve_ticket_any', $context['ticket']['dept']) || shd_allowed_to('shd_resolve_ticket_own', $context['ticket']['dept']) && $context['ticket']['ticket_opener']);
    $context['can_unsolve'] = $context['ticket']['closed'] && (shd_allowed_to('shd_unresolve_ticket_any', $context['ticket']['dept']) || shd_allowed_to('shd_unresolve_ticket_own', $context['ticket']['dept']) && $context['ticket']['ticket_opener']);
    $context['can_silent_update'] = $context['can_reply'] && shd_allowed_to('shd_silent_update', $context['ticket']['dept']);
    // And off we go
    $context['ticket_navigation'] = array();
    $context['ticket_navigation'][] = array('url' => $scripturl . '?action=helpdesk;sa=editticket;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'edit', 'alt' => '*', 'display' => !$context['ticket']['closed'] && !$context['ticket']['deleted'] && (shd_allowed_to('shd_edit_ticket_any', $context['ticket']['dept']) || $context['ticket']['ticket_opener'] && shd_allowed_to('shd_edit_ticket_own', $context['ticket']['dept'])), 'text' => 'shd_ticket_edit');
    $context['ticket_navigation'][] = array('url' => $scripturl . '?action=helpdesk;sa=markunread;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'unread', 'alt' => '*', 'display' => !$context['ticket']['closed'] && !$context['ticket']['deleted'], 'text' => 'shd_ticket_markunread');
    $context['ticket_navigation'][] = array('url' => $scripturl . '?action=helpdesk;sa=resolveticket;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'resolved', 'alt' => '*', 'display' => $context['can_solve'], 'text' => 'shd_ticket_resolved');
    $context['ticket_navigation'][] = array('url' => $scripturl . '?action=helpdesk;sa=resolveticket;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'unresolved', 'alt' => '*', 'display' => $context['can_unsolve'], 'text' => 'shd_ticket_unresolved');
    // This is always going to be a pain. But it should be possible to contextualise it nicely.
    // And while this isn't quite as nicely formatted as a single nice array definition,
    // imagine trying to debug the display and text options later if it were done with nested ternaries... *shudder*
    $context['ajax_assign'] = false;
    $assign_nav = array('url' => $scripturl . '?action=helpdesk;sa=assign;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'assign', 'alt' => '*', 'text' => '', 'display' => false);
    if (shd_allowed_to('shd_assign_ticket_any', $context['ticket']['dept'])) {
        $assign_nav['display'] = shd_allowed_to('shd_staff', $context['ticket']['dept']) && !$context['ticket']['closed'] && !$context['ticket']['deleted'];
        $assign_nav['text'] = empty($context['ticket']['id_member_assigned']) ? 'shd_ticket_assign' : 'shd_ticket_reassign';
        $context['ajax_assign'] = $assign_nav['display'];
    } elseif (shd_allowed_to('shd_assign_ticket_own', $context['ticket']['dept'])) {
        $assign_nav['display'] = !$context['ticket']['closed'] && !$context['ticket']['deleted'] && shd_allowed_to('shd_staff', $context['ticket']['dept']) && (empty($context['ticket']['id_member_assigned']) || $context['ticket']['assigned_self']);
        // either not assigned or assigned to self
        $assign_nav['text'] = $context['ticket']['assigned_self'] ? 'shd_ticket_unassign' : 'shd_ticket_assign_self';
    }
    $context['ticket_navigation'][] = $assign_nav;
    $context['ticket_navigation'][] = array('url' => $scripturl . '?action=helpdesk;sa=deleteticket;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'delete', 'alt' => '*', 'display' => !$context['ticket']['closed'] && !$context['ticket']['deleted'] && (shd_allowed_to('shd_delete_ticket_any', $context['ticket']['dept']) || shd_allowed_to('shd_delete_ticket_own', $context['ticket']['dept']) && $context['ticket']['ticket_opener']), 'text' => 'shd_ticket_delete', 'onclick' => 'return confirm(' . JavaScriptEscape($txt['shd_delete_confirm']) . ');');
    $context['ticket_navigation'][] = array('url' => $scripturl . '?action=helpdesk;sa=restoreticket;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'restore', 'alt' => '*', 'display' => $context['ticket']['deleted'] && (shd_allowed_to('shd_restore_ticket_any', $context['ticket']['dept']) || shd_allowed_to('shd_restore_ticket_own', $context['ticket']['dept']) && $context['ticket']['ticket_opener']), 'text' => 'shd_ticket_restore');
    $context['ticket_navigation'][] = array('url' => $scripturl . '?action=helpdesk;sa=permadelete;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'delete', 'alt' => '*', 'display' => $context['ticket']['deleted'] && shd_allowed_to('shd_delete_recycling', $context['ticket']['dept']), 'text' => 'shd_delete_permanently', 'onclick' => 'return confirm(' . JavaScriptEscape($txt['shd_delete_permanently_confirm']) . ');');
    $context['ticket_navigation'][] = array('url' => $scripturl . '?action=helpdesk;sa=movedept;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'movedept', 'alt' => '*', 'display' => $context['can_move_dept'], 'text' => 'shd_move_dept');
    $context['ticket_navigation'][] = array('url' => $scripturl . '?action=helpdesk;sa=tickettotopic;ticket=' . $context['ticket']['id'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'icon' => 'tickettotopic', 'alt' => '*', 'display' => $context['shd_can_move_to_topic'] && !$context['ticket']['closed'] && !$context['ticket']['deleted'] && ($context['ticket']['deleted_replies'] == 0 || shd_allowed_to('shd_access_recyclebin', $context['ticket']['dept'])), 'text' => 'shd_ticket_move_to_topic');
    // While we're at it, set up general navigation for this ticket. We'll sort out access to the action log later.
    $context['navigation']['replies'] = array('text' => 'shd_go_to_replies', 'lang' => true, 'url' => '#replies');
    $context['navigation']['ticketlog'] = array('text' => 'shd_go_to_action_log', 'test' => 'display_ticket_log', 'lang' => true, 'url' => '#ticket_log_header');
    // If we are going SMF style with the navigation, we need to rework the structure a wee bit.
    // No sense making a new array, mind, just fix up the existing one a touch, and don't do this on the master as we don't always need it.
    if (empty($modSettings['shd_ticketnav_style']) || !in_array($modSettings['shd_ticketnav_style'], array('sd', 'sdcompact', 'smf'))) {
        $modSettings['shd_ticketnav_style'] = 'sd';
    }
    if ($modSettings['shd_ticketnav_style'] == 'smf') {
        foreach ($context['ticket_navigation'] as $key => $button) {
            $context['can_' . $button['text']] = $button['display'];
            $context['ticket_navigation'][$key] += array('lang' => true, 'test' => 'can_' . $button['text'], 'image' => 'shd_ticket_' . $button['icon'] . '.png');
        }
    }
    // Quick reply stuffs
    require_once $sourcedir . '/sd_source/SimpleDesk-Post.php';
    require_once $sourcedir . '/Subs-Editor.php';
    loadTemplate('sd_template/SimpleDesk-Post');
    $context['ticket_form']['ticket'] = $context['ticket_id'];
    $context['ticket_form']['num_allowed_attachments'] = empty($modSettings['attachmentNumPerPostLimit']) || $modSettings['shd_attachments_mode'] == 'ticket' ? -1 : $modSettings['attachmentNumPerPostLimit'];
    $context['ticket_form']['do_attach'] = shd_allowed_to('shd_post_attachment', $context['ticket']['dept']);
    $context['ticket_form']['num_replies'] = $context['ticket']['num_replies'];
    $context['ticket_form']['disable_smileys'] = empty($modSettings['shd_allow_ticket_smileys']);
    shd_posting_additional_options();
    if ($context['can_reply']) {
        shd_load_canned_replies();
    }
    $context['can_ping'] = $context['can_reply'] && shd_allowed_to('shd_singleton_email', $context['ticket']['dept']);
    // Set up the fancy editor
    shd_postbox('shd_message', '', array('post_button' => $txt['shd_reply_ticket']));
    // Lastly, our magic AJAX stuff ;D and we know we already made html_headers exist in SimpleDesk.php, score!
    $context['html_headers'] .= '
	<script type="text/javascript"><!-- // --><![CDATA[
	var sSessI = "' . $context['session_id'] . '";
	var sSessV = "' . $context['session_var'] . '";';
    if ($context['ticket']['privacy']['can_change']) {
        $context['html_headers'] .= '
	var shd_ajax_problem = ' . JavaScriptEscape($txt['shd_ajax_problem']) . ';
	var privacyCtl = new shd_privacyControl({
		ticket: ' . $context['ticket_id'] . ',
		sUrl: smf_scripturl + "?action=helpdesk;sa=ajax;op=privacy;ticket=' . $context['ticket_id'] . '",
		sSession: sSessV + "=" + sSessI,
		sSrcA: "privlink",
		sDestSpan: "privacy"
	});';
    }
    if ($context['ticket']['urgency']['increase'] || $context['ticket']['urgency']['decrease']) {
        $context['html_headers'] .= '
	var urgencyCtl = new shd_urgencyControl({
		ticket: ' . $context['ticket_id'] . ',
		sUrl: smf_scripturl + "?action=helpdesk;sa=ajax;op=urgency;ticket=' . $context['ticket_id'] . ';change=",
		sSession: sSessV + "=" + sSessI,
		sDestSpan: "urgency",
		aButtons: ["up", "down"],
		aButtonOps: { up: "increase", down: "decrease" }
	});';
    }
    if (!empty($options['display_quick_reply'])) {
        $context['html_headers'] .= '
	var oQuickReply = new QuickReply({
		bDefaultCollapsed: ' . (!empty($options['display_quick_reply']) && $options['display_quick_reply'] == 2 ? 'false' : 'true') . ',
		iTicketId: ' . $context['ticket_id'] . ',
		iStart: ' . $context['start'] . ',
		sScriptUrl: smf_scripturl,
		sImagesUrl: "' . $settings['images_url'] . '",
		sContainerId: "quickReplyOptions",
		sImageId: "quickReplyExpand",
		sImageCollapsed: "collapse.png",
		sImageExpanded: "expand.png",
		sJumpAnchor: "quickreply",
		sHeaderId: "quickreplyheader",
		sFooterId: "quickreplyfooter"
	});';
    }
    $context['html_headers'] .= '
	var oCustomFields = new CustomFields({
		sImagesUrl: "' . $settings['images_url'] . '",
		sContainerId: "additional_info",
		sImageId: "shd_custom_fields_swap",
		sImageCollapsed: "collapse.png",
		sImageExpanded: "expand.png",
		sHeaderId: "additionalinfoheader",
		sFooterId: "additional_info_footer",
	});';
    if (!empty($options['display_quick_reply']) && $context['can_go_advanced']) {
        $context['html_headers'] .= '
	function goAdvanced()
	{
		document.getElementById("shd_bbcbox").style.display = ' . (!empty($modSettings['shd_allow_ticket_bbc']) ? '""' : '"none"') . ';
		document.getElementById("shd_smileybox").style.display = ' . (!empty($modSettings['shd_allow_ticket_smileys']) ? '""' : '"none"') . ';
		document.getElementById("shd_attach_container").style.display = ' . (!empty($context['ticket_form']['do_attach']) ? '""' : '"none"') . ';
		document.getElementById("shd_goadvancedbutton").style.display = "none";' . (!empty($context['controls']['richedit']['shd_message']['rich_active']) ? '
		oEditorHandle_shd_message.toggleView(true);' : '') . '
	}
	';
    }
    $context['html_headers'] .= '
	// ]' . ']></script>';
    $context['shd_display'] = true;
    $context['controls']['richedit']['shd_message']['rich_active'] = 0;
    // we don't want it by default!
    // Register this form in the session variables.
    checkSubmitOnce('register');
    // Should we load and display this ticket's action log?
    $context['display_ticket_log'] = !empty($modSettings['shd_display_ticket_logs']) && (shd_allowed_to('shd_view_ticket_logs_any', $context['ticket']['dept']) || shd_allowed_to('shd_view_ticket_logs_own', $context['ticket']['dept']) && $context['ticket']['ticket_opener']);
    // If yes, go ahead and load the log entries (Re-using a couple of functions from the ACP)
    if (!empty($context['display_ticket_log'])) {
        require_once $sourcedir . '/sd_source/Subs-SimpleDeskAdmin.php';
        $context['ticket_log'] = shd_load_action_log_entries(-1, 10, '', '', 'la.id_ticket = ' . $context['ticket_id']);
        $context['ticket_log_count'] = shd_count_action_log_entries('la.id_ticket = ' . $context['ticket_id']);
        $context['ticket_full_log'] = allowedTo('admin_forum') || shd_allowed_to('admin_helpdesk', 0);
    }
    // What about related tickets?
    $context['create_relationships'] = shd_allowed_to('shd_create_relationships', $context['ticket']['dept']);
    $context['display_relationships'] = (shd_allowed_to('shd_view_relationships', $context['ticket']['dept']) || $context['create_relationships']) && empty($modSettings['shd_disable_relationships']);
    $context['delete_relationships'] = shd_allowed_to('shd_delete_relationships', $context['ticket']['dept']);
    if (!empty($context['display_relationships'])) {
        shd_load_relationships($context['ticket_id']);
        if ($context['relationships_count'] == 0 && empty($context['create_relationships'])) {
            $context['display_relationships'] = false;
        }
    }
    // And, of course, notifications. If we can see the ticket, we can do something with notifications.
    $context['display_notifications'] = array('show' => false, 'preferences' => array(), 'can_change' => shd_allowed_to(array('shd_view_profile_own', 'shd_view_profile_any'), 0) && shd_allowed_to(array('shd_view_preferences_own', 'shd_view_preferences_any'), 0), 'can_monitor' => shd_allowed_to('shd_monitor_ticket_any', $context['ticket']['dept']) || $context['ticket']['ticket_opener'] && shd_allowed_to('shd_monitor_ticket_own', $context['ticket']['dept']), 'is_monitoring' => false, 'can_ignore' => shd_allowed_to('shd_ignore_ticket_any', $context['ticket']['dept']) || $context['ticket']['ticket_opener'] && shd_allowed_to('shd_ignore_ticket_own', $context['ticket']['dept']), 'is_ignoring' => false);
    $notify_state = NOTIFY_PREFS;
    $query = $smcFunc['db_query']('', '
		SELECT notify_state
		FROM {db_prefix}helpdesk_notify_override
		WHERE id_member = {int:user}
			AND id_ticket = {int:ticket}', array('user' => $context['user']['id'], 'ticket' => $context['ticket_id']));
    if ($smcFunc['db_num_rows']($query) != 0) {
        list($notify_state) = $smcFunc['db_fetch_row']($query);
    }
    $smcFunc['db_free_result']($query);
    $context['display_notifications']['is_monitoring'] = $notify_state == NOTIFY_ALWAYS;
    $context['display_notifications']['is_ignoring'] = $notify_state == NOTIFY_NEVER;
    if ($notify_state != NOTIFY_NEVER) {
        if ($context['ticket']['ticket_opener'] && !empty($context['shd_preferences']['notify_new_reply_own'])) {
            $context['display_notifications']['preferences'][] = 'yourticket';
        }
        if ($context['ticket']['assigned_self'] && !empty($context['shd_preferences']['notify_new_reply_assigned'])) {
            $context['display_notifications']['preferences'][] = 'assignedyou';
        }
        if (!empty($context['shd_preferences']['notify_new_reply_previous'])) {
            // We need to query to see if we've replied here before - but we don't need to check ticket access.
            $query = $smcFunc['db_query']('', '
				SELECT COUNT(hdtr.id_msg)
				FROM {db_prefix}helpdesk_tickets AS hdt
					INNER JOIN {db_prefix}helpdesk_ticket_replies AS hdtr ON (hdt.id_ticket = hdtr.id_ticket)
				WHERE hdt.id_ticket = {int:ticket}
					AND hdtr.id_member = {int:user}
					AND hdtr.id_msg != hdt.id_first_msg', array('ticket' => $context['ticket_id'], 'user' => $context['user']['id']));
            list($count) = $smcFunc['db_fetch_row']($query);
            $smcFunc['db_free_result']($query);
            if (!empty($count)) {
                $context['display_notifications']['preferences'][] = 'priorreply';
            }
        }
        if (!empty($context['shd_preferences']['notify_new_reply_any'])) {
            $context['display_notifications']['preferences'][] = 'anyreply';
        }
    }
    if (!empty($context['display_notifications']['preferences']) || $context['display_notifications']['can_monitor'] || $context['display_notifications']['can_ignore'] || $context['display_notifications']['can_change']) {
        $context['display_notifications']['show'] = true;
    }
}
Example #8
0
/**
 *	Loads the main SimpleDesk information page for forum administrators.
 *
 *	This function is the main focus point for information about SimpleDesk in the admin panel, primarily it collects the following for the template:
 *	<ul>
 *	<li>list of helpdesk staff</li>
 *	<li>totals of tickets in the system (open/closed/deleted)</li>
 *	<li>credits</li>
 *	<li>also, in the template, whether this is a current or outdated version of SimpleDesk</li>
 *	</ul>
 *
 *	Since 1.1, it also receives the requests for subactions for action log and support page (since these are sub menu items) but simply directs them onward.
 *
 *	@see shd_admin_action_log()
 *	@see shd_admin_support()
 *
 *	@since 1.0
*/
function shd_admin_info()
{
    global $context, $settings, $scripturl, $txt, $sourcedir, $smcFunc;
    $subactions = array('main' => array('function' => false, 'icon' => 'simpledesk.png', 'title' => $txt['shd_admin_info']), 'actionlog' => array('function' => 'shd_admin_action_log', 'icon' => 'log.png', 'title' => $txt['shd_admin_actionlog_title']), 'support' => array('function' => 'shd_admin_support', 'icon' => 'support.png', 'title' => $txt['shd_admin_support']));
    $context[$context['admin_menu_name']]['tab_data'] = array('description' => $txt['shd_admin_options_desc'], 'tabs' => array('main' => array('description' => '<strong>' . $txt['hello_guest'] . ' ' . $context['user']['name'] . '!</strong><br />' . $txt['shd_admin_info_desc']), 'actionlog' => array('description' => $txt['shd_admin_actionlog_desc'] . '<br />' . (!empty($modSettings['shd_disable_action_log']) ? '<span class="smalltext">' . $txt['shd_action_log_disabled'] . '</span>' : '')), 'support' => array('description' => $txt['shd_admin_support_desc'])));
    call_integration_hook('shd_hook_hdadmininfo', array(&$subactions));
    $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subactions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'main';
    // Now that we have validated the subaction.
    $context[$context['admin_menu_name']]['tab_data']['title'] = '<img src="' . $settings['images_url'] . '/admin/shd/' . $subactions[$_REQUEST['sa']]['icon'] . '" class="icon" alt="*" />' . $subactions[$_REQUEST['sa']]['title'];
    // Are we doing the main page, or leaving here?
    if (!empty($subactions[$_REQUEST['sa']]['function'])) {
        $subactions[$_REQUEST['sa']]['function']();
        return;
    }
    // Get a list of the staff members of the helpdesk.
    $members = shd_members_allowed_to('shd_staff');
    $query = shd_db_query('', '
		SELECT id_member, real_name
		FROM {db_prefix}members
		WHERE id_member IN ({array_int:members})
		ORDER BY real_name', array('members' => $members));
    // Note this just gets everyone, doesn't worry about limiting it - IMO that's something for the template to decide.
    $context['staff'] = array();
    while ($row = $smcFunc['db_fetch_assoc']($query)) {
        $context['staff'][] = shd_profile_link($row['real_name'], $row['id_member']);
    }
    $smcFunc['db_free_result']($query);
    // Make we sure give these slackers some credit. After all, they made sumfin fer ya!
    shd_credits();
    $context['total_tickets'] = shd_count_helpdesk_tickets();
    $context['open_tickets'] = shd_count_helpdesk_tickets('open');
    $context['closed_tickets'] = shd_count_helpdesk_tickets('closed');
    $context['recycled_tickets'] = shd_count_helpdesk_tickets('recycled');
    // Final stuff before we go.
    $context['page_title'] = $txt['shd_admin_title'];
    $context['sub_template'] = 'shd_admin';
}