Skip to content
Snippets Groups Projects
ajax.tickets.php 41.1 KiB
Newer Older
Jared Hancock's avatar
Jared Hancock committed
<?php
/*********************************************************************
    ajax.tickets.php

    AJAX interface for tickets

    Peter Rotich <peter@osticket.com>
    Copyright (c)  2006-2013 osTicket
Jared Hancock's avatar
Jared Hancock committed
    http://www.osticket.com

    Released under the GNU General Public License WITHOUT ANY WARRANTY.
    See LICENSE.TXT for details.

    vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/

if(!defined('INCLUDE_DIR')) die('403');

include_once(INCLUDE_DIR.'class.ticket.php');
require_once(INCLUDE_DIR.'class.ajax.php');
require_once(INCLUDE_DIR.'class.note.php');
include_once INCLUDE_DIR . 'class.thread_actions.php';
Jared Hancock's avatar
Jared Hancock committed

class TicketsAjaxAPI extends AjaxController {
    function lookup() {
        global $thisstaff;
        $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
        $tickets=array();
        $visibility = Q::any(array(
            'staff_id' => $thisstaff->getId(),
            'team_id__in' => $thisstaff->teams->values_flat('team_id'),
        ));
        if (!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts())) {
            $visibility->add(array('dept_id__in' => $depts));
        }
        $hits = TicketModel::objects()
            ->filter($visibility)
            ->values('user__default_email__address')
            ->annotate(array(
                'number' => new SqlCode('null'),
                'tickets' => SqlAggregate::COUNT('ticket_id', true)))
        $q = $_REQUEST['q'];
        // Drop at sign in email addresses
        $q = str_replace('@', ' ', $q);

        global $ost;
        $hits = $ost->searcher->find($q, $hits)
            ->order_by(new SqlCode('__relevance__'), QuerySet::DESC);

        if (preg_match('/\d{2,}[^*]/', $q, $T = array())) {
            $hits = TicketModel::objects()
                ->values('user__default_email__address', 'number')
                ->annotate(array(
                    'tickets' => new SqlCode('1'),
                    '__relevance__' => new SqlCode(1)
                ))
                ->filter(array('number__startswith' => $q))
                ->limit($limit)
                ->union($hits);
        }
        elseif (!count($hits) && preg_match('`\w$`u', $q)) {
            // Do wild-card fulltext search
            $_REQUEST['q'] = $q.'*';
            return $this->lookup();
            $email = $T['user__default_email__address'];
            if ($T['number']) {
                $tickets[] = array('id'=>$T['number'], 'value'=>$T['number'],
                    'info'=>"{$T['number']}{$email}",
                    'matches'=>$_REQUEST['q']);
            }
            else {
                $tickets[] = array('email'=>$email, 'value'=>$email,
                    'info'=>"$email ($count)", 'matches'=>$_REQUEST['q']);
            }
        return $this->json_encode($tickets);
Jared Hancock's avatar
Jared Hancock committed
    }

    function acquireLock($tid) {
        global $cfg, $thisstaff;
        if(!$cfg || !$cfg->getLockTime() || $cfg->getTicketLockMode() == Lock::MODE_DISABLED)
            Http::response(418, $this->encode(array('id'=>0, 'retry'=>false)));

        if(!$tid || !is_numeric($tid) || !$thisstaff)
Jared Hancock's avatar
Jared Hancock committed
            return 0;
        if (!($ticket = Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff))
            return $this->encode(array('id'=>0, 'retry'=>false, 'msg'=>__('Lock denied!')));
Jared Hancock's avatar
Jared Hancock committed
        //is the ticket already locked?
        if ($ticket->isLocked() && ($lock=$ticket->getLock()) && !$lock->isExpired()) {
Jared Hancock's avatar
Jared Hancock committed
            /*Note: Ticket->acquireLock does the same logic...but we need it here since we need to know who owns the lock up front*/
            //Ticket is locked by someone else.??
            if ($lock->getStaffId() != $thisstaff->getId())
                return $this->json_encode(array('id'=>0, 'retry'=>false,
                    'msg' => sprintf(__('Currently locked by %s'),
                        $lock->getStaff()->getAvatarAndName())
Jared Hancock's avatar
Jared Hancock committed
            //Ticket already locked by staff...try renewing it.
            $lock->renew(); //New clock baby!
        } elseif(!($lock=$ticket->acquireLock($thisstaff->getId(),$cfg->getLockTime()))) {
            //unable to obtain the lock..for some really weired reason!
            //Client should watch for possible loop on retries. Max attempts?
            return $this->json_encode(array('id'=>0, 'retry'=>true));
        return $this->json_encode(array(
            'id'=>$lock->getId(), 'time'=>$lock->getTime(),
            'code' => $lock->getCode()
        ));
    function renewLock($id, $ticketId) {
Jared Hancock's avatar
Jared Hancock committed
        global $thisstaff;

        if (!$id || !is_numeric($id) || !$thisstaff)
            Http::response(403, $this->encode(array('id'=>0, 'retry'=>false)));
        if (!($lock = Lock::lookup($id)))
            Http::response(404, $this->encode(array('id'=>0, 'retry'=>'acquire')));
        if (!($ticket = Ticket::lookup($ticketId)) || $ticket->lock_id != $lock->lock_id)
            // Ticket / Lock mismatch
            Http::response(400, $this->encode(array('id'=>0, 'retry'=>false)));

        if (!$lock->getStaffId() || $lock->isExpired())
            // Said lock doesn't exist or is is expired — fetch a new lock
            return self::acquireLock($ticket->getId());

        if ($lock->getStaffId() != $thisstaff->getId())
            // user doesn't own the lock anymore??? sorry...try to next time.
            Http::response(403, $this->encode(array('id'=>0, 'retry'=>false,
                'msg' => sprintf(__('Currently locked by %s'),
                    $lock->getStaff->getAvatarAndName())
            ))); //Give up...

        // Ensure staff still has access
        if (!$ticket->checkStaffPerm($thisstaff))
            Http::response(403, $this->encode(array('id'=>0, 'retry'=>false,
                'msg' => sprintf(__('You no longer have access to #%s.'),
                $ticket->getNumber())
            )));

        // Renew the lock.
        // Failure here is not an issue since the lock is not expired yet.. client need to check time!
        $lock->renew();

        return $this->encode(array('id'=>$lock->getId(), 'time'=>$lock->getTime(),
            'code' => $lock->getCode()));
    function releaseLock($id) {
Jared Hancock's avatar
Jared Hancock committed
        global $thisstaff;

        if (!$id || !is_numeric($id) || !$thisstaff)
            Http::response(403, $this->encode(array('id'=>0, 'retry'=>true)));
        if (!($lock = Lock::lookup($id)))
            Http::response(404, $this->encode(array('id'=>0, 'retry'=>true)));

        // You have to own the lock
        if ($lock->getStaffId() != $thisstaff->getId()) {
        // Can't be expired
        if ($lock->isExpired()) {
            return 1;
        return $lock->release() ? 1 : 0;
Jared Hancock's avatar
Jared Hancock committed
    }

    function previewTicket ($tid) {
        global $thisstaff;

        if(!$thisstaff || !($ticket=Ticket::lookup($tid))
                || !$ticket->checkStaffPerm($thisstaff))
            Http::response(404, __('No such ticket'));
        include STAFFINC_DIR . 'templates/ticket-preview.tmpl.php';
Peter Rotich's avatar
Peter Rotich committed
    function viewUser($tid) {
        global $thisstaff;

        if(!$thisstaff
                || !($ticket=Ticket::lookup($tid))
                || !$ticket->checkStaffPerm($thisstaff))
Peter Rotich's avatar
Peter Rotich committed
            Http::response(404, 'No such ticket');


        if(!($user = User::lookup($ticket->getOwnerId())))
Peter Rotich's avatar
Peter Rotich committed
            Http::response(404, 'Unknown user');


        $info = array(
            'title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(),
                Format::htmlchars($user->getName()))
            );
Peter Rotich's avatar
Peter Rotich committed

        ob_start();
        include(STAFFINC_DIR . 'templates/user.tmpl.php');
        $resp = ob_get_contents();
        ob_end_clean();
        return $resp;

    }

    function updateUser($tid) {

        global $thisstaff;

        if(!$thisstaff
                || !($ticket=Ticket::lookup($tid))
                || !$ticket->checkStaffPerm($thisstaff)
                || !($user = User::lookup($ticket->getOwnerId())))
Peter Rotich's avatar
Peter Rotich committed
            Http::response(404, 'No such ticket/user');

        $errors = array();
        if($user->updateInfo($_POST, $errors, true))
Peter Rotich's avatar
Peter Rotich committed
             Http::response(201, $user->to_json());

        $forms = $user->getForms();

        $info = array(
            'title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(),
                Format::htmlchars($user->getName()))
            );
Peter Rotich's avatar
Peter Rotich committed

        ob_start();
        include(STAFFINC_DIR . 'templates/user.tmpl.php');
        $resp = ob_get_contents();
        ob_end_clean();
        return $resp;
    }

    function changeUserForm($tid) {
        global $thisstaff;

        if(!$thisstaff
                || !($ticket=Ticket::lookup($tid))
                || !$ticket->checkStaffPerm($thisstaff))
Peter Rotich's avatar
Peter Rotich committed
            Http::response(404, 'No such ticket');


        $user = User::lookup($ticket->getOwnerId());
Peter Rotich's avatar
Peter Rotich committed

        $info = array(
                'title' => sprintf(__('Change user for ticket #%s'), $ticket->getNumber())
Peter Rotich's avatar
Peter Rotich committed
                );

        return self::_userlookup($user, null, $info);
Peter Rotich's avatar
Peter Rotich committed
    }

    function _userlookup($user, $form, $info) {
Peter Rotich's avatar
Peter Rotich committed

        ob_start();
        include(STAFFINC_DIR . 'templates/user-lookup.tmpl.php');
        $resp = ob_get_contents();
        ob_end_clean();
        return $resp;

    }

    function manageForms($ticket_id) {
        $forms = DynamicFormEntry::forTicket($ticket_id);
        $info = array('action' => '#tickets/'.Format::htmlchars($ticket_id).'/forms/manage');
        include(STAFFINC_DIR . 'templates/form-manage.tmpl.php');
    }

    function updateForms($ticket_id) {
        global $thisstaff;

        if (!$thisstaff)
            Http::response(403, "Login required");
        elseif (!($ticket = Ticket::lookup($ticket_id)))
            Http::response(404, "No such ticket");
        elseif (!$ticket->checkStaffPerm($thisstaff))
            Http::response(403, "Access Denied");
        elseif (!isset($_POST['forms']))
            Http::response(422, "Send updated forms list");

        // Add new forms
        $forms = DynamicFormEntry::forTicket($ticket_id);
        foreach ($_POST['forms'] as $sort => $id) {
            $found = false;
            foreach ($forms as $e) {
                if ($e->get('form_id') == $id) {
                    $e->set('sort', $sort);
                    $e->save();
                    $found = true;
                    break;
                }
            }
            // New form added
            if (!$found && ($new = DynamicForm::lookup($id))) {
                $f = $new->instanciate();
                $f->set('sort', $sort);
                $f->setTicketId($ticket_id);
                $f->save();
            }
        }

        // Deleted forms
        foreach ($forms as $idx => $e) {
            if (!in_array($e->get('form_id'), $_POST['forms']))
                $e->delete();
        }

        Http::response(201, 'Successfully managed');
    }

Peter Rotich's avatar
Peter Rotich committed
    function cannedResponse($tid, $cid, $format='text') {
        global $thisstaff, $cfg;

        if (!($ticket = Ticket::lookup($tid))
                || !$ticket->checkStaffPerm($thisstaff))
Peter Rotich's avatar
Peter Rotich committed
            Http::response(404, 'Unknown ticket ID');
Peter Rotich's avatar
Peter Rotich committed


        if ($cid && !is_numeric($cid)) {
            if (!($response=$ticket->getThread()->getVar($cid)))
Peter Rotich's avatar
Peter Rotich committed
                Http::response(422, 'Unknown ticket variable');
Peter Rotich's avatar
Peter Rotich committed

            // Ticket thread variables are assumed to be quotes
            $response = "<br/><blockquote>{$response->asVar()}</blockquote><br/>";
Peter Rotich's avatar
Peter Rotich committed
            //  Return text if html thread is not enabled
            if (!$cfg->isRichTextEnabled())
Peter Rotich's avatar
Peter Rotich committed
                $response = Format::html2text($response, 90);
            else
                $response = Format::viewableImages($response);
Peter Rotich's avatar
Peter Rotich committed

            // XXX: assuming json format for now.
            return Format::json_encode(array('response' => $response));
        }

        if (!$cfg->isRichTextEnabled())
Peter Rotich's avatar
Peter Rotich committed
            $format.='.plain';

        $varReplacer = function (&$var) use($ticket) {
            return $ticket->replaceVars($var);
        };

        include_once(INCLUDE_DIR.'class.canned.php');
        if (!$cid || !($canned=Canned::lookup($cid)) || !$canned->isEnabled())
            Http::response(404, 'No such premade reply');

        return $canned->getFormattedResponse($format, $varReplacer);
    }
Peter Rotich's avatar
Peter Rotich committed

Peter Rotich's avatar
Peter Rotich committed
    function transfer($tid) {
        global $thisstaff;

        if (!($ticket=Ticket::lookup($tid)))
            Http::response(404, __('No such ticket'));

        if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_TRANSFER))
            Http::response(403, __('Permission Denied'));

        $errors = array();

        $info = array(
                ':title' => sprintf(__('Ticket #%s: %s'),
                    $ticket->getNumber(),
                    __('Transfer')),
                ':action' => sprintf('#tickets/%d/transfer',
                    $ticket->getId())
                );

        $form = $ticket->getTransferForm($_POST);
        if ($_POST && $form->isValid()) {
            if ($ticket->transfer($form, $errors)) {
                $_SESSION['::sysmsgs']['msg'] = sprintf(
                        __('%s successfully'),
                        sprintf(
                            __('%s transferred to %s department'),
                            __('Ticket'),
                            $ticket->getDept()
                            )
                        );
                Http::response(201, $ticket->getId());
            }

            $form->addErrors($errors);
            $info['error'] = $errors['err'] ?: __('Unable to transfer ticket');
        }

        $info['dept_id'] = $info['dept_id'] ?: $ticket->getDeptId();

        include STAFFINC_DIR . 'templates/transfer.tmpl.php';
    }


    function assign($tid, $to=null) {
        global $thisstaff;

        if (!($ticket=Ticket::lookup($tid)))
            Http::response(404, __('No such ticket'));

        if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN))
            Http::response(403, __('Permission Denied'));

        $errors = array();
        $info = array(
                ':title' => sprintf(__('Ticket #%s: %s'),
                    $ticket->getNumber(),
                    $ticket->isAssigned() ? __('Reassign') :  __('Assign')),
                ':action' => sprintf('#tickets/%d/assign%s',
                    $ticket->getId(),
                    ($to  ? "/$to": '')),
                );
        if ($ticket->isAssigned()) {
            $info['notice'] = sprintf(__('%s is currently assigned to %s'),
                    __('Ticket'),
                    $ticket->getAssigned());
        }

        $form = $ticket->getAssignmentForm($_POST);
        if ($_POST && $form->isValid()) {
            if ($ticket->assign($form, $errors)) {
                $_SESSION['::sysmsgs']['msg'] = sprintf(
                        __('%s successfully'),
                        sprintf(
                            __('%s assigned to %s'),
                            __('Ticket'),
                            $form->getAssignee())
                        );
                Http::response(201, $ticket->getId());
            }

            $form->addErrors($errors);
            $info['error'] = $errors['err'] ?: __('Unable to assign ticket');
        }

        include STAFFINC_DIR . 'templates/assign.tmpl.php';
    }

    function massProcess($action)  {
        global $thisstaff;

        $actions = array(
                'transfer' => array(
                    'verbed' => __('transferred'),
                    ),
                'assign' => array(
                    'verbed' => __('assigned'),
                    ),
                'delete' => array(
                    'verbed' => __('deleted'),
                    ),
                'reopen' => array(
                    'verbed' => __('reopen'),
                    ),
                'close' => array(
                    'verbed' => __('closed'),
                    ),
                );

        if (!isset($actions[$action]))
            Http::response(404, __('Unknown action'));


        $info = $errors = $e = array();
        $inc = null;
        $i = $count = 0;
        if ($_POST) {
            if (!$_POST['tids'] || !($count=count($_POST['tids'])))
                $errors['err'] = sprintf(
                        __('You must select at least %s.'),
                        __('one ticket'));
        } else {
            $count  =  $_REQUEST['count'];
        }
        switch ($action) {
        case 'assign':
            $inc = 'assign.tmpl.php';
            $info[':action'] = '#tickets/mass/assign';
            $info[':title'] = sprintf('Assign %s',
                    _N('selected ticket', 'selected tickets', $count));
            $form = AssignmentForm::instantiate($_POST);
            if ($_POST && $form->isValid()) {
                foreach ($_POST['tids'] as $tid) {
                    if (($t=Ticket::lookup($tid))
                            // Make sure the agent is allowed to
                            // access and assign the task.
                            && $t->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN)
                            // Do the assignment
                            && $t->assign($form, $e)
                            )
                        $i++;
                }

                if (!$i) {
                    $info['error'] = sprintf(
                            __('Unable to %1$s %2$s'),
                            __('assign'),
                            _N('selected ticket', 'selected tickets', $count));
                }
            }
            break;
        case 'transfer':
            $inc = 'transfer.tmpl.php';
            $info[':action'] = '#tickets/mass/transfer';
            $info[':title'] = sprintf('Transfer %s',
                    _N('selected ticket', 'selected tickets', $count));
            $form = TransferForm::instantiate($_POST);
            if ($_POST && $form->isValid()) {
                foreach ($_POST['tids'] as $tid) {
                    if (($t=Ticket::lookup($tid))
                            // Make sure the agent is allowed to
                            // access and transfer the task.
                            && $t->checkStaffPerm($thisstaff, Ticket::PERM_TRANSFER)
                            // Do the transfer
                            && $t->transfer($form, $e)
                            )
                        $i++;
                }

                if (!$i) {
                    $info['error'] = sprintf(
                            __('Unable to %1$s %2$s'),
                            __('transfer'),
                            _N('selected ticket', 'selected tickets', $count));
                }
            }
            break;
        case 'delete':
            $inc = 'delete.tmpl.php';
            $info[':action'] = '#tickets/mass/delete';
            $info[':title'] = sprintf('Delete %s',
                    _N('selected ticket', 'selected tickets', $count));

            $info[':placeholder'] = sprintf(__(
                        'Optional reason for deleting %s'),
                    _N('selected ticket', 'selected tickets', $count));
            $info['warn'] = sprintf(__(
                        'Are you sure you want to DELETE %s?'),
                    _N('selected ticket', 'selected tickets', $count));
            $info[':extra'] = sprintf('<strong>%s</strong>',
                        __('Deleted tickets CANNOT be recovered, including any associated attachments.')
                        );

            // Generic permission check.
            if (!$thisstaff->hasPerm(Ticket::PERM_DELETE, false))
                $errors['err'] = sprintf(
                        __('You do not have permission to %s %s'),
                        __('delete'),
                        __('tickets'));


            if ($_POST && !$errors) {
                foreach ($_POST['tids'] as $tid) {
                    if (($t=Ticket::lookup($tid))
                            && $t->checkStaffPerm($thisstaff, Ticket::PERM_DELETE)
                            && $t->delete($_POST['comments'], $e)
Peter Rotich's avatar
Peter Rotich committed
                            )
                        $i++;
                }

                if (!$i) {
                    $info['error'] = sprintf(
                            __('Unable to %1$s %2$s'),
                            __('delete'),
                            _N('selected ticket', 'selected tickets', $count));
                }
            }
            break;
        default:
            Http::response(404, __('Unknown action'));
        }

        if ($_POST && $i) {

            // Assume success
            if ($i==$count) {
                $msg = sprintf(__('Successfully %s %s.'),
                        $actions[$action]['verbed'],
                        sprintf(__('%1$d %2$s'),
                            $count,
                            _N('selected ticket', 'selected tickets', $count))
                        );
                $_SESSION['::sysmsgs']['msg'] = $msg;
            } else {
                $warn = sprintf(
                        __('%1$d of %2$d %3$s %4$s'), $i, $count,
                        _N('selected ticket', 'selected tickets',
                            $count),
                        $actions[$action]['verbed']);
                $_SESSION['::sysmsgs']['warn'] = $warn;
            }
            Http::response(201, 'processed');
        } elseif($_POST && !isset($info['error'])) {
            $info['error'] = $errors['err'] ?: sprintf(
                    __('Unable to %1$s  %2$s'),
                    __('process'),
                    _N('selected ticket', 'selected tickets', $count));
        }

        if ($_POST)
            $info = array_merge($info, Format::htmlchars($_POST));

        include STAFFINC_DIR . "templates/$inc";
        //  Copy checked tickets to the form.
        echo "
        <script type=\"text/javascript\">
        $(function() {
            $('form#tickets input[name=\"tids[]\"]:checkbox:checked')
            .each(function() {
                $('<input>')
                .prop('type', 'hidden')
                .attr('name', 'tids[]')
                .val($(this).val())
                .appendTo('form.mass-action');
            });
        });
        </script>";

    }

    function changeTicketStatus($tid, $status, $id=0) {
Peter Rotich's avatar
Peter Rotich committed
        global $thisstaff;

        if (!$thisstaff)
            Http::response(403, 'Access denied');
        elseif (!$tid
                || !($ticket=Ticket::lookup($tid))
                || !$ticket->checkStaffPerm($thisstaff))
Peter Rotich's avatar
Peter Rotich committed
            Http::response(404, 'Unknown ticket #');

        $role = $thisstaff->getRole($ticket->getDeptId());

Peter Rotich's avatar
Peter Rotich committed
        $info = array();
Peter Rotich's avatar
Peter Rotich committed
        $state = null;
Peter Rotich's avatar
Peter Rotich committed
        switch($status) {
            case 'open':
            case 'reopen':
                $state = 'open';
                break;
            case 'close':
                if (!$role->hasPerm(TicketModel::PERM_CLOSE))
Peter Rotich's avatar
Peter Rotich committed
                    Http::response(403, 'Access denied');
                $state = 'closed';

                // Check if ticket is closeable
                if (is_string($closeable=$ticket->isCloseable()))
                    $info['warn'] =  $closeable;

Peter Rotich's avatar
Peter Rotich committed
                break;
            case 'delete':
                if (!$role->hasPerm(TicketModel::PERM_DELETE))
Peter Rotich's avatar
Peter Rotich committed
                    Http::response(403, 'Access denied');
                $state = 'deleted';
                break;
            default:
Peter Rotich's avatar
Peter Rotich committed
                $state = $ticket->getStatus()->getState();
                $info['warn'] = sprintf('%s %s',
                        __('Unknown or invalid'), __('status'));
Peter Rotich's avatar
Peter Rotich committed
        $info['status_id'] = $id ?: $ticket->getStatusId();
Peter Rotich's avatar
Peter Rotich committed

Peter Rotich's avatar
Peter Rotich committed
        return self::_changeTicketStatus($ticket, $state, $info);
Peter Rotich's avatar
Peter Rotich committed
    function setTicketStatus($tid) {
Peter Rotich's avatar
Peter Rotich committed
        global $thisstaff, $ost;

        if (!$thisstaff)
            Http::response(403, 'Access denied');
        elseif (!$tid
                || !($ticket=Ticket::lookup($tid))
                || !$ticket->checkStaffPerm($thisstaff))
Peter Rotich's avatar
Peter Rotich committed
            Http::response(404, 'Unknown ticket #');

        $errors = $info = array();
Peter Rotich's avatar
Peter Rotich committed
        if (!$_POST['status_id']
                || !($status= TicketStatus::lookup($_POST['status_id'])))
Peter Rotich's avatar
Peter Rotich committed
            $errors['status_id'] = sprintf('%s %s',
                    __('Unknown or invalid'), __('status'));
        elseif ($status->getId() == $ticket->getStatusId())
            $errors['err'] = sprintf(__('Ticket already set to %s status'),
                    __($status->getName()));
        elseif (($role = $thisstaff->getRole($ticket->getDeptId()))) {
Peter Rotich's avatar
Peter Rotich committed
            // Make sure the agent has permission to set the status
            switch(mb_strtolower($status->getState())) {
                case 'open':
                    if (!$role->hasPerm(TicketModel::PERM_CLOSE)
                            && !$role->hasPerm(TicketModel::PERM_CREATE))
                        $errors['err'] = sprintf(__('You do not have permission %s.'),
                                __('to reopen tickets'));
Peter Rotich's avatar
Peter Rotich committed
                    break;
                case 'closed':
                    if (!$role->hasPerm(TicketModel::PERM_CLOSE))
                        $errors['err'] = sprintf(__('You do not have permission %s.'),
                                __('to resolve/close tickets'));
Peter Rotich's avatar
Peter Rotich committed
                    break;
                case 'deleted':
                    if (!$role->hasPerm(TicketModel::PERM_DELETE))
                        $errors['err'] = sprintf(__('You do not have permission %s.'),
                                __('to archive/delete tickets'));
Peter Rotich's avatar
Peter Rotich committed
                    break;
                default:
                    $errors['err'] = sprintf('%s %s',
                            __('Unknown or invalid'), __('status'));
Peter Rotich's avatar
Peter Rotich committed
            }
        } else {
            $errors['err'] = __('Access denied');
Peter Rotich's avatar
Peter Rotich committed
        $state = strtolower($status->getState());

        if (!$errors && $ticket->setStatus($status, $_REQUEST['comments'], $errors)) {
Peter Rotich's avatar
Peter Rotich committed

Peter Rotich's avatar
Peter Rotich committed
            if ($state == 'deleted') {
Peter Rotich's avatar
Peter Rotich committed
                $msg = sprintf('%s %s',
                        sprintf(__('Ticket #%s'), $ticket->getNumber()),
                        __('deleted sucessfully')
                        );
Peter Rotich's avatar
Peter Rotich committed
            } elseif ($state != 'open') {
                 $msg = sprintf(__('%s status changed to %s'),
                         sprintf(__('Ticket #%s'), $ticket->getNumber()),
                         $status->getName());
Peter Rotich's avatar
Peter Rotich committed
            } else {
                $msg = sprintf(
Peter Rotich's avatar
Peter Rotich committed
                        __('%s status changed to %s'),
                        __('Ticket'),
Peter Rotich's avatar
Peter Rotich committed
                        $status->getName());
            }

            $_SESSION['::sysmsgs']['msg'] = $msg;

Peter Rotich's avatar
Peter Rotich committed
            Http::response(201, 'Successfully processed');
        } elseif (!$errors['err']) {
            $errors['err'] =  __('Error updating ticket status');
Peter Rotich's avatar
Peter Rotich committed
        $state = $state ?: $ticket->getStatus()->getState();
Peter Rotich's avatar
Peter Rotich committed
        $info['status_id'] = $status
            ? $status->getId() : $ticket->getStatusId();
Peter Rotich's avatar
Peter Rotich committed
        return self::_changeTicketStatus($ticket, $state, $info, $errors);
Peter Rotich's avatar
Peter Rotich committed
    }
Peter Rotich's avatar
Peter Rotich committed
    function changeSelectedTicketsStatus($status, $id=0) {
Peter Rotich's avatar
Peter Rotich committed
        global $thisstaff, $cfg;

        if (!$thisstaff)
            Http::response(403, 'Access denied');

        $state = null;
        $info = array();
        switch($status) {
            case 'open':
            case 'reopen':
                $state = 'open';
                break;
            case 'close':
                if (!$thisstaff->hasPerm(TicketModel::PERM_CLOSE, false))
Peter Rotich's avatar
Peter Rotich committed
                    Http::response(403, 'Access denied');
                $state = 'closed';
                break;
            case 'delete':
                if (!$thisstaff->hasPerm(TicketModel::PERM_DELETE, false))
Peter Rotich's avatar
Peter Rotich committed
                    Http::response(403, 'Access denied');

                $state = 'deleted';
                break;
            default:
                $info['warn'] = sprintf('%s %s',
                        __('Unknown or invalid'), __('status'));
Peter Rotich's avatar
Peter Rotich committed
        }

        $info['status_id'] = $id;

Peter Rotich's avatar
Peter Rotich committed
        return self::_changeSelectedTicketsStatus($state, $info);
Peter Rotich's avatar
Peter Rotich committed
    function setSelectedTicketsStatus($state) {
Peter Rotich's avatar
Peter Rotich committed
        global $thisstaff, $ost;

        $errors = $info = array();
Peter Rotich's avatar
Peter Rotich committed
        if (!$thisstaff || !$thisstaff->canManageTickets())
            $errors['err'] = sprintf('%s %s',
                    sprintf(__('You do not have permission %s.'),
                        __('to mass manage tickets')),
                    __('Contact admin for such access'));
Peter Rotich's avatar
Peter Rotich committed
        elseif (!$_REQUEST['tids'] || !count($_REQUEST['tids']))
            $errors['err']=sprintf(__('You must select at least %s.'),
                    __('one ticket'));
        elseif (!($status= TicketStatus::lookup($_REQUEST['status_id'])))
            $errors['status_id'] = sprintf('%s %s',
                    __('Unknown or invalid'), __('status'));
        elseif (!$errors) {
            // Make sure the agent has permission to set the status
            switch(mb_strtolower($status->getState())) {
                case 'open':
                    if (!$thisstaff->hasPerm(TicketModel::PERM_CLOSE, false)
                            && !$thisstaff->hasPerm(TicketModel::PERM_CREATE, false))
                        $errors['err'] = sprintf(__('You do not have permission %s.'),
                                __('to reopen tickets'));
Peter Rotich's avatar
Peter Rotich committed
                    break;
                case 'closed':
                    if (!$thisstaff->hasPerm(TicketModel::PERM_CLOSE, false))
                        $errors['err'] = sprintf(__('You do not have permission %s.'),
                                __('to resolve/close tickets'));
Peter Rotich's avatar
Peter Rotich committed
                    break;
                case 'deleted':
                    if (!$thisstaff->hasPerm(TicketModel::PERM_DELETE, false))
                        $errors['err'] = sprintf(__('You do not have permission %s.'),
                                __('to archive/delete tickets'));
Peter Rotich's avatar
Peter Rotich committed
                    break;
                default:
                    $errors['err'] = sprintf('%s %s',
                            __('Unknown or invalid'), __('status'));
        $count = count($_REQUEST['tids']);
Peter Rotich's avatar
Peter Rotich committed
        if (!$errors) {
            $i = 0;
            $comments = $_REQUEST['comments'];
            foreach ($_REQUEST['tids'] as $tid) {
Peter Rotich's avatar
Peter Rotich committed
                if (($ticket=Ticket::lookup($tid))
                        && $ticket->getStatusId() != $status->getId()
                        && $ticket->checkStaffPerm($thisstaff)
                        && $ticket->setStatus($status, $comments, $errors))
            if (!$i) {
                $errors['err'] = $errors['err']
                    ?: sprintf(__('Unable to change status for %s'),
                        _N('the selected ticket', 'any of the selected tickets', $count));
Peter Rotich's avatar
Peter Rotich committed
            else {
                // Assume success
                if ($i==$count) {
Peter Rotich's avatar
Peter Rotich committed

                    if (!strcasecmp($status->getState(), 'deleted')) {
                        $msg = sprintf(__( 'Successfully deleted %s.'),
                                _N('selected ticket', 'selected tickets',
                                    $count));
                    } else {
                       $msg = sprintf(
                            __(
                                /* 1$ will be 'selected ticket(s)', 2$ is the new status */
Peter Rotich's avatar
Peter Rotich committed
                                'Successfully changed status of %1$s to %2$s'),
Peter Rotich's avatar
Peter Rotich committed
                            _N('selected ticket', 'selected tickets',
                                $count),
                            $status->getName());
Peter Rotich's avatar
Peter Rotich committed
                    }

                    $_SESSION['::sysmsgs']['msg'] = $msg;
Peter Rotich's avatar
Peter Rotich committed
                } else {
Peter Rotich's avatar
Peter Rotich committed

                    if (!strcasecmp($status->getState(), 'deleted')) {
                        $warn = sprintf(__('Successfully deleted %s.'),
                                sprintf(__('%1$d of %2$d selected tickets'),
                                    $i, $count)
                                );
                    } else {

                        $warn = sprintf(
                                __('%1$d of %2$d %3$s status changed to %4$s'),$i, $count,
                                _N('selected ticket', 'selected tickets',
                                    $count),
                                $status->getName());
                    }

                    $_SESSION['::sysmsgs']['warn'] = $warn;
Peter Rotich's avatar
Peter Rotich committed
                }

                Http::response(201, 'Successfully processed');
            }
        }

Peter Rotich's avatar
Peter Rotich committed
        return self::_changeSelectedTicketsStatus($state, $info, $errors);
    }

    function triggerThreadAction($ticket_id, $thread_id, $action) {
        $thread = ThreadEntry::lookup($thread_id);
        if (!$thread)
            Http::response(404, 'No such ticket thread entry');
        if ($thread->getThread()->getObjectId() != $ticket_id)
            Http::response(404, 'No such ticket thread entry');

        $valid = false;
        foreach ($thread->getActions() as $group=>$list) {
            foreach ($list as $name=>$A) {
                if ($A->getId() == $action) {
                    $valid = true; break;
                }
            }
        }
        if (!$valid)
            Http::response(400, 'Not a valid action for this thread');

        $thread->triggerAction($action);
    }

Peter Rotich's avatar
Peter Rotich committed
    private function _changeSelectedTicketsStatus($state, $info=array(), $errors=array()) {

        $count = $_REQUEST['count'] ?:
            ($_REQUEST['tids'] ?  count($_REQUEST['tids']) : 0);

        $info['title'] = sprintf(__('%1$s Tickets &mdash; %2$d selected'),
                TicketStateField::getVerb($state),
Peter Rotich's avatar
Peter Rotich committed
                 $count);
Peter Rotich's avatar
Peter Rotich committed
        if (!strcasecmp($state, 'deleted')) {
Peter Rotich's avatar
Peter Rotich committed
            $info['warn'] = sprintf(__(
                        'Are you sure you want to DELETE %s?'),
                    _N('selected ticket', 'selected tickets', $count)
                    );
Peter Rotich's avatar
Peter Rotich committed
            $info['extra'] = sprintf('<strong>%s</strong>', __(
                        'Deleted tickets CANNOT be recovered, including any associated attachments.')
                    );

            $info['placeholder'] = sprintf(__(
                        'Optional reason for deleting %s'),
                    _N('selected ticket', 'selected tickets', $count));
        }

        $info['status_id'] = $info['status_id'] ?: $_REQUEST['status_id'];
        $info['comments'] = Format::htmlchars($_REQUEST['comments']);
Peter Rotich's avatar
Peter Rotich committed

        return self::_changeStatus($state, $info, $errors);
Peter Rotich's avatar
Peter Rotich committed
    private function _changeTicketStatus($ticket, $state, $info=array(), $errors=array()) {

        $verb = TicketStateField::getVerb($state);

        $info['action'] = sprintf('#tickets/%d/status', $ticket->getId());
        $info['title'] = sprintf(__(
                    /* 1$ will be a verb, like 'open', 2$ will be the ticket number */
                    '%1$s Ticket #%2$s'),
                $verb ?: $state,
                $ticket->getNumber()
                );

        // Deleting?
        if (!strcasecmp($state, 'deleted')) {

            $info['placeholder'] = sprintf(__(
                        'Optional reason for deleting %s'),
                    __('this ticket'));
            $info[ 'warn'] = sprintf(__(
                        'Are you sure you want to DELETE %s?'),
                        __('this ticket'));
            //TODO: remove message below once we ship data retention plug
            $info[ 'extra'] = sprintf('<strong>%s</strong>',
                        __('Deleted tickets CANNOT be recovered, including any associated attachments.')
                        );
        }

        $info['status_id'] = $info['status_id'] ?: $ticket->getStatusId();
        $info['comments'] = Format::htmlchars($_REQUEST['comments']);

        return self::_changeStatus($state, $info, $errors);
    }

    private function _changeStatus($state, $info=array(), $errors=array()) {
Peter Rotich's avatar
Peter Rotich committed

        if ($info && isset($info['errors']))
Peter Rotich's avatar
Peter Rotich committed
            $errors = array_merge($errors, $info['errors']);
Peter Rotich's avatar
Peter Rotich committed

        if (!$info['error'] && isset($errors['err']))
            $info['error'] = $errors['err'];

        include(STAFFINC_DIR . 'templates/ticket-status.tmpl.php');
    }

    function tasks($tid) {
        global $thisstaff;

        if (!($ticket=Ticket::lookup($tid))
                || !$ticket->checkStaffPerm($thisstaff))
            Http::response(404, 'Unknown ticket');

         include STAFFINC_DIR . 'ticket-tasks.inc.php';
    }

    function addTask($tid) {