Skip to content
Snippets Groups Projects
ajax.tickets.php 30.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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;
    
            if(!is_numeric($_REQUEST['q']))
    
                return self::lookupByEmail();
    
            $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(Q::any(array(
                    'number__startswith' => $_REQUEST['q'],
                )))
                ->filter($visibility)
                ->values('number', 'user__emails__address')
                ->annotate(array('tickets' => SqlAggregate::COUNT('ticket_id')))
                ->order_by('-created')
                ->limit($limit);
    
            foreach ($hits as $T) {
                $tickets[] = array('id'=>$T['number'], 'value'=>$T['number'],
                    'info'=>"{$T['number']}{$T['user__emails__address']}",
                    'matches'=>$_REQUEST['q']);
    
            if (!$tickets)
                return self::lookupByEmail();
    
            return $this->json_encode($tickets);
        }
    
    
        function lookupByEmail() {
    
            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(Q::any(array(
                    'user__emails__address__contains' => $_REQUEST['q'],
                    'user__name__contains' => $_REQUEST['q'],
                    'user__account__username' => $_REQUEST['q'],
                    'user__org__name__contains' => $_REQUEST['q'],
                )))
                ->filter($visibility)
                ->values('user__emails__address')
                ->annotate(array('tickets' => SqlAggregate::COUNT('ticket_id')))
                ->limit($limit);
    
            foreach ($hits as $T) {
                $email = $T['user__emails__address'];
                $count = $T['tickets'];
                $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(!$tid || !is_numeric($tid) || !$thisstaff || !$cfg || !$cfg->getLockTime())
    
    Jared Hancock's avatar
    Jared Hancock committed
                return 0;
    
            if(!($ticket = Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff))
    
                return $this->json_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()) {
                /*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'=>__('Unable to acquire lock.')));
    
    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()
            ));
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function renewLock($tid, $id) {
            global $thisstaff;
    
    
    clonemeagain's avatar
    clonemeagain committed
            if(!$tid || !is_numeric($tid) || !$id || !is_numeric($id) || !$thisstaff)
    
    Jared Hancock's avatar
    Jared Hancock committed
                return $this->json_encode(array('id'=>0, 'retry'=>true));
    
            if (!($ticket = Ticket::lookup($tid)))
                return $this->json_encode(array('id'=>0, 'retry'=>true));
    
            $lock = $ticket->getLock();
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(!$lock || !$lock->getStaffId() || $lock->isExpired()) //Said lock doesn't exist or is is expired
                return self::acquireLock($tid); //acquire the lock
    
    Jared Hancock's avatar
    Jared Hancock committed
            if($lock->getStaffId()!=$thisstaff->getId()) //user doesn't own the lock anymore??? sorry...try to next time.
                return $this->json_encode(array('id'=>0, 'retry'=>false)); //Give up...
    
    Jared Hancock's avatar
    Jared Hancock committed
            //Renew the lock.
            $lock->renew(); //Failure here is not an issue since the lock is not expired yet.. client need to check time!
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->json_encode(array('id'=>$lock->getId(), 'time'=>$lock->getTime()));
        }
    
        function releaseLock($tid, $id=0) {
            global $thisstaff;
    
    
            if (!($ticket = Ticket::lookup($tid))) {
                return 0;
            }
    
            if ($id) {
                // Fetch the lock from the ticket
                if (!($lock = $ticket->getLock())) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                    return 1;
    
                }
                // Identify the lock by the ID number
                if ($lock->getId() != $id) {
    
                }
                // You have to own the lock
                if ($lock->getStaffId() != $thisstaff->getId()) {
                    return 0;
                }
                // Can't be expired
                if ($lock->isExpired()) {
                    return 1;
                }
                return $lock->release() ? 1 : 0;
    
            return Lock::removeStaffLocks($thisstaff->getId(), $ticket) ? 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
    
    
        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';
                    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) {
            global $thisstaff;
    
    
            if (!($ticket=Ticket::lookup($tid)))
    
                Http::response(404, 'Unknown ticket');
    
    
            if (!$ticket->checkStaffPerm($thisstaff, Task::PERM_CREATE))
                Http::response(403, 'Permission denied');
    
    
            $info=$errors=array();
    
    
            if ($_POST) {
                Draft::deleteForNamespace(
                        sprintf('ticket.%d.task', $ticket->getId()),
                        $thisstaff->getId());
    
                // Default form
    
    Peter Rotich's avatar
    Peter Rotich committed
                $form = TaskForm::getInstance();
                $form->setSource($_POST);
    
                // Internal form
                $iform = TaskForm::getInternalForm($_POST);
                $isvalid = true;
                if (!$iform->isValid())
                    $isvalid = false;
                if (!$form->isValid())
                    $isvalid = false;
    
                if ($isvalid) {
                    $vars = $_POST;
                    $vars['object_id'] = $ticket->getId();
                    $vars['object_type'] = ObjectModel::OBJECT_TYPE_TICKET;
                    $vars['default_formdata'] = $form->getClean();
                    $vars['internal_formdata'] = $iform->getClean();
                    $desc = $form->getField('description');
                    if ($desc
                            && $desc->isAttachmentsEnabled()
                            && ($attachments=$desc->getWidget()->getAttachments()))
                        $vars['cannedattachments'] = $attachments->getClean();
                    $vars['staffId'] = $thisstaff->getId();
                    $vars['poster'] = $thisstaff;
                    $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
                    if (($task=Task::create($vars, $errors)))
                        Http::response(201, $task->getId());
                }
    
    
                $info['error'] = __('Error adding task - try again!');
            }
    
            $info['action'] = sprintf('#tickets/%d/add-task', $ticket->getId());
            $info['title'] = sprintf(
                    __( 'Ticket #%1$s: %2$s'),
                    $ticket->getNumber(),
                    _('Add New Task')
                    );
    
             include STAFFINC_DIR . 'templates/task.tmpl.php';
        }
    
    Peter Rotich's avatar
    Peter Rotich committed
    
        function task($tid, $id) {
            global $thisstaff;
    
            if (!($ticket=Ticket::lookup($tid))
                    || !$ticket->checkStaffPerm($thisstaff))
                Http::response(404, 'Unknown ticket');
    
            // Lookup task and check access
            if (!($task=Task::lookup($id))
                    || !$task->checkStaffPerm($thisstaff))
                Http::response(404, 'Unknown task');
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $info = $errors = array();
            $note_attachments_form = new SimpleForm(array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                'attachments' => new FileUploadField(array('id'=>'attach',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'name'=>'attach:note',
                    'configuration' => array('extensions'=>'')))
            ));
    
            $reply_attachments_form = new SimpleForm(array(
                'attachments' => new FileUploadField(array('id'=>'attach',
                    'name'=>'attach:reply',
                    'configuration' => array('extensions'=>'')))
            ));
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            if ($_POST) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                $vars = $_POST;
    
    Peter Rotich's avatar
    Peter Rotich committed
                switch ($_POST['a']) {
                case 'postnote':
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $attachments = $note_attachments_form->getField('attachments')->getClean();
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $vars['cannedattachments'] = array_merge(
                        $vars['cannedattachments'] ?: array(), $attachments);
    
    Peter Rotich's avatar
    Peter Rotich committed
                    if (($note=$task->postNote($vars, $errors, $thisstaff))) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                        $msg=__('Note posted successfully');
                        // Clear attachment list
    
    Peter Rotich's avatar
    Peter Rotich committed
                        $note_attachments_form->setSource(array());
                        $note_attachments_form->getField('attachments')->reset();
    
    Peter Rotich's avatar
    Peter Rotich committed
                        Draft::deleteForNamespace('task.note.'.$task->getId(),
                                $thisstaff->getId());
                    } else {
    
    Peter Rotich's avatar
    Peter Rotich committed
                        if (!$errors['err'])
    
    Peter Rotich's avatar
    Peter Rotich committed
                            $errors['err'] = __('Unable to post the note - missing or invalid data.');
                    }
                    break;
    
    Peter Rotich's avatar
    Peter Rotich committed
                case 'postreply':
                    $attachments = $reply_attachments_form->getField('attachments')->getClean();
                    $vars['cannedattachments'] = array_merge(
                        $vars['cannedattachments'] ?: array(), $attachments);
                    if (($response=$task->postReply($vars, $errors))) {
                        $msg=__('Update posted successfully');
                        // Clear attachment list
                        $reply_attachments_form->setSource(array());
                        $reply_attachments_form->getField('attachments')->reset();
                        Draft::deleteForNamespace('task.reply.'.$task->getId(),
                                $thisstaff->getId());
                    } else {
                        if (!$errors['err'])
                            $errors['err'] = __('Unable to post the reply - missing or invalid data.');
                    }
                    break;
    
    Peter Rotich's avatar
    Peter Rotich committed
                default:
                    $errors['err'] = __('Unknown action');
                }
            }
    
            include STAFFINC_DIR . 'templates/task-view.tmpl.php';
        }