Skip to content
Snippets Groups Projects
class.ticket.php 82 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jared Hancock's avatar
    Jared Hancock committed
            $this->reload();
    
            $dept = $this->getDept();
            $email = $dept->getAutoRespEmail();
    
    Jared Hancock's avatar
    Jared Hancock committed
            //If enabled...send confirmation to user. ( New Message AutoResponse)
    
            if($email
                    && ($tpl=$dept->getTemplate())
                    && ($msg=$tpl->getNewMessageAutorepMsgTemplate())) {
    
                $msg = $this->replaceVars($msg->asArray(),
    
                                array('signature' => ($dept && $dept->isPublic())?$dept->getSignature():''));
    
    Jared Hancock's avatar
    Jared Hancock committed
    
                //Reply separator tag.
                if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
    
                    $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body'];
    
                if (!$message)
                    $message = $this->getLastMessage();
    
                    'inreplyto'=>$message->getEmailMessageId(),
    
                    'references'=>$message->getEmailReferences());
    
                $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'],
                    null, $options);
    
        function onAssign($assignee, $comments, $alert=true) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            global $cfg, $thisstaff;
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if($this->isClosed()) $this->reopen(); //Assigned tickets must be open - otherwise why assign?
    
    
            //Assignee must be an object of type Staff or Team
            if(!$assignee || !is_object($assignee)) return false;
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->reload();
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $comments = $comments?$comments:'Ticket assignment';
            $assigner = $thisstaff?$thisstaff:'SYSTEM (Auto Assignment)';
    
            //Log an internal note - no alerts on the internal note.
    
            $note = $this->logNote('Ticket Assigned to '.$assignee->getName(),
                $comments, $assigner, false);
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            //See if we need to send alerts
            if(!$alert || !$cfg->alertONAssignment()) return true; //No alerts!
    
            $dept = $this->getDept();
    
            if(!$dept
                    || !($tpl = $dept->getTemplate())
                    || !($email = $cfg->getAlertEmail()))
                return true;
    
    
            //recipients
            $recipients=array();
            if(!strcasecmp(get_class($assignee), 'Staff')) {
                if($cfg->alertStaffONAssignment())
                    $recipients[] = $assignee;
            } elseif(!strcasecmp(get_class($assignee), 'Team')) {
                if($cfg->alertTeamMembersONAssignment() && ($members=$assignee->getMembers()))
                    $recipients+=$members;
                elseif($cfg->alertTeamLeadONAssignment() && ($lead=$assignee->getTeamLead()))
                    $recipients[] = $lead;
            }
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            //Get the message template
    
            if($recipients && ($msg=$tpl->getAssignedAlertMsgTemplate())) {
    
                $msg = $this->replaceVars($msg->asArray(),
    
                            array('comments' => $comments,
                                  'assignee' => $assignee,
                                  'assigner' => $assigner
    
    Jared Hancock's avatar
    Jared Hancock committed
                //Send the alerts.
                $sentlist=array();
    
                    'inreplyto'=>$note->getEmailMessageId(),
    
                    'references'=>$note->getEmailReferences());
    
                foreach( $recipients as $k=>$staff) {
    
                    if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
    
                    $alert = $this->replaceVars($msg, array('recipient' => $staff));
                    $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], null, $options);
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $sentlist[] = $staff->getEmail();
    
    Jared Hancock's avatar
    Jared Hancock committed
       function onOverdue($whine=true, $comments="") {
    
    Jared Hancock's avatar
    Jared Hancock committed
            global $cfg;
    
    
            if($whine && ($sla=$this->getSLA()) && !$sla->alertOnOverdue())
                $whine = false;
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            //check if we need to send alerts.
    
            if(!$whine
                    || !$cfg->alertONOverdueTicket()
                    || !($dept = $this->getDept()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                return true;
    
            //Get the message template
    
            if(($tpl = $dept->getTemplate())
                    && ($msg=$tpl->getOverdueAlertMsgTemplate())
                    && ($email=$cfg->getAlertEmail())) {
    
                $msg = $this->replaceVars($msg->asArray(),
                    array('comments' => $comments));
    
    Jared Hancock's avatar
    Jared Hancock committed
    
                //recipients
                $recipients=array();
                //Assigned staff or team... if any
    
                if($this->isAssigned() && $cfg->alertAssignedONOverdueTicket()) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                    if($this->getStaffId())
                        $recipients[]=$this->getStaff();
                    elseif($this->getTeamId() && ($team=$this->getTeam()) && ($members=$team->getMembers()))
                        $recipients=array_merge($recipients, $members);
                } elseif($cfg->alertDeptMembersONOverdueTicket() && !$this->isAssigned()) {
                    //Only alerts dept members if the ticket is NOT assigned.
    
    Peter Rotich's avatar
    Peter Rotich committed
                    if(($members=$dept->getMembers()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                        $recipients=array_merge($recipients, $members);
                }
                //Always alert dept manager??
                if($cfg->alertDeptManagerONOverdueTicket() && $dept && ($manager=$dept->getManager()))
                    $recipients[]= $manager;
    
                $sentlist=array();
    
                foreach( $recipients as $k=>$staff) {
                    if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
    
                    $alert = $this->replaceVars($msg, array('recipient' => $staff));
                    $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], null);
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $sentlist[] = $staff->getEmail();
    
        //ticket obj as variable = ticket number.
        function asVar() {
           return $this->getNumber();
    
        function getVar($tag) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            global $cfg;
    
    
            if($tag && is_callable(array($this, 'get'.ucfirst($tag))))
    
                return call_user_func(array($this, 'get'.ucfirst($tag)));
    
    
            switch(strtolower($tag)) {
    
                case 'phone_number':
                    return $this->getPhoneNumber();
                    break;
                case 'auth_token':
                    return $this->getAuthToken();
                    break;
                case 'client_link':
                    return sprintf('%s/view.php?t=%s&e=%s&a=%s',
                            $cfg->getBaseUrl(), $this->getNumber(), $this->getEmail(), $this->getAuthToken());
                    break;
                case 'staff_link':
                    return sprintf('%s/scp/tickets.php?id=%d', $cfg->getBaseUrl(), $this->getId());
                    break;
                case 'create_date':
                    return Format::date(
    
                            Misc::db2gmtime($this->getCreateDate()),
                            $cfg->getTZOffset(),
                            $cfg->observeDaylightSaving());
                    break;
                 case 'due_date':
                    $duedate ='';
    
                    if($this->getEstDueDate())
    
                        $duedate = Format::date(
                                $cfg->getDateTimeFormat(),
    
                                Misc::db2gmtime($this->getEstDueDate()),
    
                                $cfg->getTZOffset(),
                                $cfg->observeDaylightSaving());
    
                    return $duedate;
                    break;
                case 'close_date';
                    $closedate ='';
                    if($this->isClosed())
                        $duedate = Format::date(
                                $cfg->getDateTimeFormat(),
                                Misc::db2gmtime($this->getCloseDate()),
                                $cfg->getTZOffset(),
                                $cfg->observeDaylightSaving());
    
                    return $closedate;
                    break;
    
                case 'user':
                    return $this->getOwner();
                    break;
    
    Jared Hancock's avatar
    Jared Hancock committed
                default:
                    if (isset($this->_answers[$tag]))
    
                        // The answer object is retrieved here which will
                        // automatically invoke the toString() method when the
                        // answer is coerced into text
    
                        return (string)$this->_answers[$tag];
    
    
            return false;
        }
    
        //Replace base variables.
        function replaceVars($input, $vars = array()) {
            global $ost;
    
            $vars = array_merge($vars, array('ticket' => $this));
    
            return $ost->replaceTemplateVariables($input, $vars);
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function markUnAnswered() {
            return (!$this->isAnswered() || $this->setAnsweredState(0));
        }
    
        function markAnswered() {
            return ($this->isAnswered() || $this->setAnsweredState(1));
        }
    
        function markOverdue($whine=true) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            global $cfg;
    
    Jared Hancock's avatar
    Jared Hancock committed
            if($this->isOverdue())
                return true;
    
    
            $sql='UPDATE '.TICKET_TABLE.' SET isoverdue=1, updated=NOW() '
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' WHERE ticket_id='.db_input($this->getId());
    
            if(!db_query($sql) || !db_affected_rows())
                return false;
    
    
            $this->logEvent('overdue');
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->onOverdue($whine);
    
    Jared Hancock's avatar
    Jared Hancock committed
            return true;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function clearOverdue() {
    
    
    Peter Rotich's avatar
    Peter Rotich committed
                return true;
    
    
            //NOTE: Previously logged overdue event is NOT annuled.
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $sql='UPDATE '.TICKET_TABLE.' SET isoverdue=0, updated=NOW() ';
    
    Peter Rotich's avatar
    Peter Rotich committed
            //clear due date if it's in the past
    
            if($this->getDueDate() && Misc::db2gmtime($this->getDueDate()) <= Misc::gmtime())
    
    Peter Rotich's avatar
    Peter Rotich committed
                $sql.=', duedate=NULL';
    
    
            //Clear SLA if est. due date is in the past
    
            if($this->getSLADueDate() && Misc::db2gmtime($this->getSLADueDate()) <= Misc::gmtime())
    
    Peter Rotich's avatar
    Peter Rotich committed
            $sql.=' WHERE ticket_id='.db_input($this->getId());
    
            return (db_query($sql) && db_affected_rows());
        }
    
    
        //Dept Tranfer...with alert.. done by staff
    
    Jared Hancock's avatar
    Jared Hancock committed
        function transfer($deptId, $comments, $alert = true) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            global $cfg, $thisstaff;
    
            if(!$thisstaff || !$thisstaff->canTransferTickets())
    
    Jared Hancock's avatar
    Jared Hancock committed
                return false;
    
    
            $currentDept = $this->getDeptName(); //Current department
    
            if(!$deptId || !$this->setDeptId($deptId))
                return false;
    
            if($this->isClosed()) $this->reopen();
    
            $this->reload();
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(!$this->getSLAId() || $this->getSLA()->isTransient())
    
            /*** log the transfer comments as internal note - with alerts disabled - ***/
    
    Peter Rotich's avatar
    Peter Rotich committed
            $title='Ticket transfered from '.$currentDept.' to '.$this->getDeptName();
    
            $note = $this->logNote($title, $comments, $thisstaff, false);
    
            $this->logEvent('transferred');
    
            //Send out alerts if enabled AND requested
    
            if(!$alert || !$cfg->alertONTransfer() || !($dept=$this->getDept()))
                return true; //no alerts!!
    
             if(($email=$cfg->getAlertEmail())
                         && ($tpl = $dept->getTemplate())
                         && ($msg=$tpl->getTransferAlertMsgTemplate())) {
    
                $msg = $this->replaceVars($msg->asArray(),
                    array('comments' => $comments, 'staff' => $thisstaff));
    
    Jared Hancock's avatar
    Jared Hancock committed
                $recipients=array();
                //Assigned staff or team... if any
                if($this->isAssigned() && $cfg->alertAssignedONTransfer()) {
                    if($this->getStaffId())
                        $recipients[]=$this->getStaff();
                    elseif($this->getTeamId() && ($team=$this->getTeam()) && ($members=$team->getMembers()))
                        $recipients+=$members;
                } elseif($cfg->alertDeptMembersONTransfer() && !$this->isAssigned()) {
                    //Only alerts dept members if the ticket is NOT assigned.
    
    Peter Rotich's avatar
    Peter Rotich committed
                    if(($members=$dept->getMembers()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                        $recipients+=$members;
                }
    
                //Always alert dept manager??
                if($cfg->alertDeptManagerONTransfer() && $dept && ($manager=$dept->getManager()))
                    $recipients[]= $manager;
    
    Jared Hancock's avatar
    Jared Hancock committed
                $sentlist=array();
    
                    'inreplyto'=>$note->getEmailMessageId(),
    
                    'references'=>$note->getEmailReferences());
    
                foreach( $recipients as $k=>$staff) {
                    if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
    
                    $alert = $this->replaceVars($msg, array('recipient' => $staff));
                    $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], null, $options);
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $sentlist[] = $staff->getEmail();
    
    Jared Hancock's avatar
    Jared Hancock committed
                }
             }
    
             return true;
        }
    
        function assignToStaff($staff, $note, $alert=true) {
    
            if(!is_object($staff) && !($staff=Staff::lookup($staff)))
                return false;
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(!$this->setStaffId($staff->getId()))
                return false;
    
    
            $this->onAssign($staff, $note, $alert);
    
            $this->logEvent('assigned');
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            return true;
        }
    
        function assignToTeam($team, $note, $alert=true) {
    
            if(!is_object($team) && !($team=Team::lookup($team)))
                return false;
    
            if(!$this->setTeamId($team->getId()))
                return false;
    
            //Clear - staff if it's a closed ticket
            //  staff_id is overloaded -> assigned to & closed by.
            if($this->isClosed())
                $this->setStaffId(0);
    
    
            $this->onAssign($team, $note, $alert);
    
            $this->logEvent('assigned');
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            return true;
        }
    
        //Assign ticket to staff or team - overloaded ID.
        function assign($assignId, $note, $alert=true) {
            global $thisstaff;
    
            $rv=0;
    
            $id=preg_replace("/[^0-9]/", "", $assignId);
    
    Jared Hancock's avatar
    Jared Hancock committed
            if($assignId[0]=='t') {
                $rv=$this->assignToTeam($id, $note, $alert);
            } elseif($assignId[0]=='s' || is_numeric($assignId)) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                $alert=($alert && $thisstaff && $thisstaff->getId()==$id)?false:$alert; //No alerts on self assigned tickets!!!
    
    Jared Hancock's avatar
    Jared Hancock committed
                //We don't care if a team is already assigned to the ticket - staff assignment takes precedence
                $rv=$this->assignToStaff($id, $note, $alert);
            }
    
            return $rv;
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
        //unassign primary assignee
        function unassign() {
    
            if(!$this->isAssigned()) //We can't release what is not assigned buddy!
                return true;
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            //We can only unassigned OPEN tickets.
            if($this->isClosed())
                return false;
    
    Peter Rotich's avatar
    Peter Rotich committed
            //Unassign staff (if any)
            if($this->getStaffId() && !$this->setStaffId(0))
                return false;
    
            //unassign team (if any)
            if($this->getTeamId() && !$this->setTeamId(0))
                return false;
    
    Peter Rotich's avatar
    Peter Rotich committed
            $this->reload();
    
            return true;
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
        function release() {
            return $this->unassign();
        }
    
    
        //Change ownership
        function changeOwner($user) {
            global $thisstaff;
    
            if (!$user
                    || ($user->getId() == $this->getOwnerId())
                    || !$thisstaff->canEditTickets())
                return false;
    
            $sql ='UPDATE '.TICKET_TABLE.' SET updated = NOW() '
                .', user_id = '.db_input($user->getId())
                .' WHERE ticket_id = '.db_input($this->getId());
    
            if (!db_query($sql) || !db_affected_rows())
                return false;
    
            $this->ht['user_id'] = $user->getId();
            $this->user = null;
    
            $this->logNote('Ticket ownership changed',
                    Format::htmlchars( sprintf('%s changed ticket ownership to %s',
                        $thisstaff->getName(), $user->getName()))
                    );
    
            return true;
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        //Insert message from client
    
        function postMessage($vars, $origin='', $alerts=true) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            global $cfg;
    
    Peter Rotich's avatar
    Peter Rotich committed
            $vars['origin'] = $origin;
    
            if(isset($vars['ip']))
    
                $vars['ip_address'] = $vars['ip'];
            elseif(!$vars['ip_address'] && $_SERVER['REMOTE_ADDR'])
                $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
    
            $errors = array();
            if(!($message = $this->getThread()->addMessage($vars, $errors)))
                return null;
    
            $this->setLastMsgId($message->getId());
    
            //Add email recipients as collaborators
    
                //TODO:  Disable collaborators added by other collaborator
                //  staff approval will be required.
                $info = array('isactive' => 1);
    
                $collabs = array();
                foreach ($vars['recipients'] as $recipient) {
                    if (($user=User::fromVars($recipient)))
    
                        if ($c=$this->addCollaborator($user, $info, $errors))
    
                            $collabs[] = sprintf('%s%s',
    
                                    $recipient['source'] ? " via {$recipient['source']}" : ''
    
                }
                //TODO: Can collaborators add others?
                if ($collabs) {
    
                    //TODO: Change EndUser to name of  user.
                    $this->logNote('Collaborators added by enduser',
    
    Peter Rotich's avatar
    Peter Rotich committed
                            implode("<br>", $collabs), 'EndUser', false);
    
            if(!$alerts) return $message; //Our work is done...
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            $autorespond = true;
    
            if ($autorespond && $message->isAutoResponse())
    
    Jared Hancock's avatar
    Jared Hancock committed
                $autorespond=false;
    
    
            $this->onMessage($autorespond, $message); //must be called b4 sending alerts to staff.
    
            $dept = $this->getDept();
    
    
            $variables = array(
                    'message' => $message,
                    'poster' => ($vars['poster'] ? $vars['poster'] : $this->getName())
                    );
            $options = array(
                    'inreplyto' => $message->getEmailMessageId(),
                    'references' => $message->getEmailReferences());
    
    Jared Hancock's avatar
    Jared Hancock committed
            //If enabled...send alert to staff (New Message Alert)
    
            if($cfg->alertONNewMessage()
                    && ($email = $cfg->getAlertEmail())
                    && ($tpl = $dept->getTemplate())
                    && ($msg = $tpl->getNewMessageAlertMsgTemplate())) {
    
                $msg = $this->replaceVars($msg->asArray(), $variables);
    
    Jared Hancock's avatar
    Jared Hancock committed
    
                //Build list of recipients and fire the alerts.
                $recipients=array();
                //Last respondent.
                if($cfg->alertLastRespondentONNewMessage() || $cfg->alertAssignedONNewMessage())
                    $recipients[]=$this->getLastRespondent();
    
    Jared Hancock's avatar
    Jared Hancock committed
                //Assigned staff if any...could be the last respondent
    
    Jared Hancock's avatar
    Jared Hancock committed
                if($this->isAssigned() && ($staff=$this->getStaff()))
                    $recipients[]=$staff;
    
    Jared Hancock's avatar
    Jared Hancock committed
                //Dept manager
                if($cfg->alertDeptManagerONNewMessage() && $dept && ($manager=$dept->getManager()))
                    $recipients[]=$manager;
    
    Jared Hancock's avatar
    Jared Hancock committed
                $sentlist=array(); //I know it sucks...but..it works.
    
    Peter Rotich's avatar
    Peter Rotich committed
                    if(!$staff || !$staff->getEmail() || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
    
                    $alert = $this->replaceVars($msg, array('recipient' => $staff));
    
                    $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], null, $options);
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $sentlist[] = $staff->getEmail();
    
            unset($variables['message']);
            $variables['message'] = $message->asVar();
    
            $info = $options + array('variables' => $variables);
            $this->activityNotice($info);
    
    
    
        function postCannedReply($canned, $msgId, $alert=true) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            global $ost, $cfg;
    
    
            if((!is_object($canned) && !($canned=Canned::lookup($canned))) || !$canned->isEnabled())
                return false;
    
            $files = array();
    
            foreach ($canned->attachments->getAll() as $file)
    
                $files[] = $file['id'];
    
            $info = array('msgId' => $msgId,
    
                          'poster' => 'SYSTEM (Canned Reply)',
    
                          'response' => $this->replaceVars($canned->getResponse()),
                          'cannedattachments' => $files);
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $errors = array();
    
            if(!($response=$this->postReply($info, $errors, false)))
                return null;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            $this->markUnAnswered();
    
    
            if(!$alert) return $response;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            $dept = $this->getDept();
    
    
            if(($email=$dept->getEmail())
                    && ($tpl = $dept->getTemplate())
                    && ($msg=$tpl->getAutoReplyMsgTemplate())) {
    
    Peter Rotich's avatar
    Peter Rotich committed
    
                if($dept && $dept->isPublic())
                    $signature=$dept->getSignature();
                else
                    $signature='';
    
    
                $msg = $this->replaceVars($msg->asArray(),
                    array('response' => $response, 'signature' => $signature));
    
    Peter Rotich's avatar
    Peter Rotich committed
    
                if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
    
                    $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body'];
    
                $attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array();
    
                    'inreplyto'=>$response->getEmailMessageId(),
    
                    'references'=>$response->getEmailReferences());
    
                $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], $attachments,
                    $options);
    
    Peter Rotich's avatar
    Peter Rotich committed
        function postReply($vars, &$errors, $alert = true) {
    
            global $thisstaff, $cfg;
    
            if(!$vars['poster'] && $thisstaff)
    
                $vars['poster'] = $thisstaff;
    
            if(!$vars['staffId'] && $thisstaff)
                $vars['staffId'] = $thisstaff->getId();
    
            if(!($response = $this->getThread()->addResponse($vars, $errors)))
                return null;
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            //Set status - if checked.
            if(isset($vars['reply_ticket_status']) && $vars['reply_ticket_status'])
                $this->setStatus($vars['reply_ticket_status']);
    
    
            if($thisstaff && $this->isOpen() && !$this->getStaffId()
                    && $cfg->autoClaimTickets())
                $this->setStaffId($thisstaff->getId()); //direct assignment;
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->onResponse(); //do house cleaning..
    
            /* email the user??  - if disabled - the bail out */
    
            if(!$alert) return $response;
    
            $dept = $this->getDept();
    
    
            if($thisstaff && $vars['signature']=='mine')
                $signature=$thisstaff->getSignature();
            elseif($vars['signature']=='dept' && $dept && $dept->isPublic())
                $signature=$dept->getSignature();
            else
                $signature='';
    
            $variables = array(
                    'response' => $response,
                    'signature' => $signature,
                    'staff' => $thisstaff,
                    'poster' => $thisstaff);
            $options = array(
                    'inreplyto' => $response->getEmailMessageId(),
                    'references' => $response->getEmailReferences());
    
            if(($email=$dept->getEmail())
                    && ($tpl = $dept->getTemplate())
                    && ($msg=$tpl->getReplyMsgTemplate())) {
    
                $msg = $this->replaceVars($msg->asArray(), $variables);
    
    Jared Hancock's avatar
    Jared Hancock committed
    
                if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
    
                    $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body'];
    
                $attachments = $cfg->emailAttachments()?$response->getAttachments():array();
    
                $email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments,
                    $options);
    
            if($vars['emailcollab']) {
                unset($variables['response']);
                $variables['message'] = $response->asVar();
                $info = $options + array('variables' => $variables);
                $this->activityNotice($info);
            }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        //Activity log - saved as internal notes WHEN enabled!!
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->logNote($title, $note, 'SYSTEM', false);
    
        // History log -- used for statistics generation (pretty reports)
    
        function logEvent($state, $annul=null, $staff=null) {
    
            global $thisstaff;
    
            if ($staff === null) {
                if ($thisstaff) $staff=$thisstaff->getUserName();
                else $staff='SYSTEM';               # XXX: Security Violation ?
            }
    
            # Annul previous entries if requested (for instance, reopening a
            # ticket will annul an 'closed' entry). This will be useful to
            # easily prevent repeated statistics.
            if ($annul) {
                db_query('UPDATE '.TICKET_EVENT_TABLE.' SET annulled=1'
                    .' WHERE ticket_id='.db_input($this->getId())
                      .' AND state='.db_input($annul));
            }
    
            return db_query('INSERT INTO '.TICKET_EVENT_TABLE
    
                .' SET ticket_id='.db_input($this->getId())
    
                .', staff_id='.db_input($this->getStaffId())
                .', team_id='.db_input($this->getTeamId())
                .', dept_id='.db_input($this->getDeptId())
                .', topic_id='.db_input($this->getTopicId())
    
                .', timestamp=NOW(), state='.db_input($state)
                .', staff='.db_input($staff))
                && db_affected_rows() == 1;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        //Insert Internal Notes
    
        function logNote($title, $note, $poster='SYSTEM', $alert=true) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            $errors = array();
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->postNote(
                    array('title' => $title, 'note' => $note),
                    $errors,
                    $poster,
                    $alert);
        }
    
    
        function postNote($vars, &$errors, $poster, $alert=true) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            global $cfg, $thisstaff;
    
    
            //Who is posting the note - staff or system?
            $vars['staffId'] = 0;
            $vars['poster'] = 'SYSTEM';
    
            if($poster && is_object($poster)) {
    
                $vars['staffId'] = $poster->getId();
                $vars['poster'] = $poster->getName();
            }elseif($poster) { //string
                $vars['poster'] = $poster;
    
            if(!($note=$this->getThread()->addNote($vars, $errors)))
                return null;
    
            //Set state: Error on state change not critical!
    
    Peter Rotich's avatar
    Peter Rotich committed
            if(isset($vars['state']) && $vars['state']) {
                if($this->setState($vars['state']))
                    $this->reload();
            }
    
    Jared Hancock's avatar
    Jared Hancock committed
            // If alerts are not enabled then return a success.
            if(!$alert || !$cfg->alertONNewNote() || !($dept=$this->getDept()))
    
            if(($email=$cfg->getAlertEmail())
                    && ($tpl = $dept->getTemplate())
                    && ($msg=$tpl->getNoteAlertMsgTemplate())) {
    
                $msg = $this->replaceVars($msg->asArray(),
                    array('note' => $note));
    
    Jared Hancock's avatar
    Jared Hancock committed
                $recipients=array();
    
    Jared Hancock's avatar
    Jared Hancock committed
                //Last respondent.
                if($cfg->alertLastRespondentONNewNote())
                    $recipients[]=$this->getLastRespondent();
    
    Jared Hancock's avatar
    Jared Hancock committed
                //Assigned staff if any...could be the last respondent
                if($cfg->alertAssignedONNewNote() && $this->isAssigned() && $this->getStaffId())
                    $recipients[]=$this->getStaff();
    
    Jared Hancock's avatar
    Jared Hancock committed
                //Dept manager
                if($cfg->alertDeptManagerONNewNote() && $dept && $dept->getManagerId())
                    $recipients[]=$dept->getManager();
    
    
                    'inreplyto'=>$note->getEmailMessageId(),
    
                    'references'=>$note->getEmailReferences());
    
    Jared Hancock's avatar
    Jared Hancock committed
                $sentlist=array();
                foreach( $recipients as $k=>$staff) {
    
                    if(!is_object($staff)
                            || !$staff->isAvailable() //Don't bother vacationing staff.
                            || in_array($staff->getEmail(), $sentlist) //No duplicates.
                            || $note->getStaffId() == $staff->getId())  //No need to alert the poster!
                        continue;
    
                    $alert = $this->replaceVars($msg, array('recipient' => $staff));
    
                    $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], null, $options);
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $sentlist[] = $staff->getEmail();
    
    Peter Rotich's avatar
    Peter Rotich committed
        //Print ticket... export the ticket thread as PDF.
    
        function pdfExport($psize='Letter', $notes=false) {
    
            require_once(INCLUDE_DIR.'class.pdf.php');
    
            $pdf = new Ticket2PDF($this, $psize, $notes);
    
            $name='Ticket-'.$this->getExtId().'.pdf';
    
            $pdf->Output($name, 'I');
    
            //Remember what the user selected - for autoselect on the next print.
            $_SESSION['PAPER_SIZE'] = $psize;
    
            $sql = 'DELETE FROM '.TICKET_TABLE.' WHERE ticket_id='.$this->getId().' LIMIT 1';
    
    Peter Rotich's avatar
    Peter Rotich committed
            if(!db_query($sql) || !db_affected_rows())
                return false;
    
    
            //delete just orphaned ticket thread & associated attachments.
            $this->getThread()->delete();
    
    
            foreach (DynamicFormEntry::forTicket($this->getId()) as $form)
                $form->delete();
    
    
            $this->deleteDrafts();
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            return true;
        }
    
    
        function deleteDrafts() {
            Draft::deleteForNamespace('ticket.%.' . $this->getId());
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function update($vars, &$errors) {
    
            global $cfg, $thisstaff;
    
    Peter Rotich's avatar
    Peter Rotich committed
            if(!$cfg || !$thisstaff || !$thisstaff->canEditTickets())
                return false;
    
    Peter Rotich's avatar
    Peter Rotich committed
            $fields=array();
            $fields['topicId']  = array('type'=>'int',      'required'=>1, 'error'=>'Help topic required');
    
            $fields['slaId']    = array('type'=>'int',      'required'=>0, 'error'=>'Select SLA');
    
    Peter Rotich's avatar
    Peter Rotich committed
            $fields['duedate']  = array('type'=>'date',     'required'=>0, 'error'=>'Invalid date - must be MM/DD/YY');
    
            $fields['note']     = array('type'=>'text',     'required'=>1, 'error'=>'Reason for the update required');
    
            $fields['user_id']  = array('type'=>'int',      'required'=>0, 'error'=>'Invalid user-id');
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            if(!Validator::process($fields, $vars, $errors) && !$errors['err'])
    
                $errors['err'] = 'Missing or invalid data - check the errors and try again';
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
    Peter Rotich's avatar
    Peter Rotich committed
                if($this->isClosed())
    
                    $errors['duedate']='Due date can NOT be set on a closed ticket';
    
    Peter Rotich's avatar
    Peter Rotich committed
                elseif(!$vars['time'] || strpos($vars['time'],':')===false)
                    $errors['time']='Select time';
                elseif(strtotime($vars['duedate'].' '.$vars['time'])===false)
    
                    $errors['duedate']='Invalid due date';
    
    Peter Rotich's avatar
    Peter Rotich committed
                elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time())
                    $errors['duedate']='Due date must be in the future';
            }
    
    Peter Rotich's avatar
    Peter Rotich committed
            if($errors) return false;
    
            $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW() '
                .' ,topic_id='.db_input($vars['topicId'])
                .' ,sla_id='.db_input($vars['slaId'])
    
                .' ,source='.db_input($vars['source'])
    
    Peter Rotich's avatar
    Peter Rotich committed
                .' ,duedate='.($vars['duedate']?db_input(date('Y-m-d G:i',Misc::dbtime($vars['duedate'].' '.$vars['time']))):'NULL');
    
            if($vars['user_id'])
                $sql.=', user_id='.db_input($vars['user_id']);
    
    Peter Rotich's avatar
    Peter Rotich committed
            if($vars['duedate']) { //We are setting new duedate...
                $sql.=' ,isoverdue=0';
            }
    
    Peter Rotich's avatar
    Peter Rotich committed
            $sql.=' WHERE ticket_id='.db_input($this->getId());
    
            if(!db_query($sql) || !db_affected_rows())
                return false;
    
            if(!$vars['note'])
                $vars['note']=sprintf('Ticket Updated by %s', $thisstaff->getName());
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $this->logNote('Ticket Updated', $vars['note'], $thisstaff);
    
    Peter Rotich's avatar
    Peter Rotich committed
            $this->reload();
    
    Jared Hancock's avatar
    Jared Hancock committed
            // Reselect SLA if transient
            if(!$this->getSLAId() || $this->getSLA()->isTransient())
                $this->selectSLAId();
    
    
            //Clear overdue flag if duedate or SLA changes and the ticket is no longer overdue.
            if($this->isOverdue()
                    && (!$this->getEstDueDate() //Duedate + SLA cleared
                        || Misc::db2gmtime($this->getEstDueDate()) > Misc::gmtime() //New due date in the future.
                        )) {
                $this->clearOverdue();
            }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            return true;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
       /*============== Static functions. Use Ticket::function(params); =============nolint*/
    
        function getIdByExtId($extId, $email=null) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            $sql ='SELECT ticket.ticket_id FROM '.TICKET_TABLE.' ticket '
    
                 .' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id'
                 .' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id'
                 .' WHERE ticket.ticketID='.db_input($extId);
    
                $sql .= ' AND email.address = '.db_input($email);
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(($res=db_query($sql)) && db_num_rows($res))
                list($id)=db_fetch_row($res);
    
            return $id;
        }
    
    
    
        function lookup($id) { //Assuming local ID is the only lookup used!
    
            return ($id
                    && is_numeric($id)
                    && ($ticket= new Ticket($id))
                    && $ticket->getId()==$id
                    && $ticket->getThread())
                ?$ticket:null;
    
        function lookupByExtId($id, $email=null) {
            return self::lookup(self:: getIdByExtId($id, $email));
    
    Jared Hancock's avatar
    Jared Hancock committed
        function genExtRandID() {
            global $cfg;
    
            //We can allow collissions...extId and email must be unique ...so same id with diff emails is ok..
            // But for clarity...we are going to make sure it is unique.
            $id=Misc::randNumber(EXT_TICKET_ID_LEN);
            if(db_num_rows(db_query('SELECT ticket_id FROM '.TICKET_TABLE.' WHERE ticketID='.db_input($id))))
                return Ticket::genExtRandID();
    
            return $id;
        }
    
    
        function getIdByMessageId($mid, $email) {
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if(!$mid || !$email)
                return 0;
    
            $sql='SELECT ticket.ticket_id FROM '.TICKET_TABLE. ' ticket '.
    
                 ' LEFT JOIN '.TICKET_THREAD_TABLE.' msg USING(ticket_id) '.
    
                 ' INNER JOIN '.TICKET_EMAIL_INFO_TABLE.' emsg ON (msg.id = emsg.message_id) '.
                 ' WHERE email_mid='.db_input($mid).' AND email='.db_input($email);
    
    Jared Hancock's avatar
    Jared Hancock committed
            $id=0;
            if(($res=db_query($sql)) && db_num_rows($res))
                list($id)=db_fetch_row($res);
    
            return $id;
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getStaffStats($staff) {
            global $cfg;
    
    Jared Hancock's avatar
    Jared Hancock committed
            /* Unknown or invalid staff */
    
            if(!$staff || (!is_object($staff) && !($staff=Staff::lookup($staff))) || !$staff->isStaff())
    
    Jared Hancock's avatar
    Jared Hancock committed
                return null;
    
    
            $where = array('ticket.staff_id='.db_input($staff->getId()));
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(($teams=$staff->getTeams()))
    
                $where[] = 'ticket.team_id IN('.implode(',', db_input(array_filter($teams))).')';
    
            if(!$staff->showAssignedOnly() && ($depts=$staff->getDepts())) //Staff with limited access just see Assigned tickets.
    
                $where[] = 'ticket.dept_id IN('.implode(',', db_input($depts)).') ';
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if(!$cfg || !($cfg->showAssignedTickets() || $staff->showAssignedTickets()))
    
                $where2 =' AND ticket.staff_id=0 ';
    
            $where = implode(' OR ', $where);
            if ($where) $where = 'AND ( '.$where.' ) ';
    
    
            $sql =  'SELECT \'open\', count( ticket.ticket_id ) AS tickets '
                    .'FROM ' . TICKET_TABLE . ' ticket '
                    .'WHERE ticket.status = \'open\' '
                    .'AND ticket.isanswered =0 '
    
                    . $where . $where2
    
    
                    .'UNION SELECT \'answered\', count( ticket.ticket_id ) AS tickets '
                    .'FROM ' . TICKET_TABLE . ' ticket '
                    .'WHERE ticket.status = \'open\' '
                    .'AND ticket.isanswered =1 '
    
    
                    .'UNION SELECT \'overdue\', count( ticket.ticket_id ) AS tickets '
                    .'FROM ' . TICKET_TABLE . ' ticket '
                    .'WHERE ticket.status = \'open\' '
                    .'AND ticket.isoverdue =1 '
    
    
                    .'UNION SELECT \'assigned\', count( ticket.ticket_id ) AS tickets '
                    .'FROM ' . TICKET_TABLE . ' ticket '
                    .'WHERE ticket.status = \'open\' '
                    .'AND ticket.staff_id = ' . db_input($staff->getId()) . ' '
    
    
                    .'UNION SELECT \'closed\', count( ticket.ticket_id ) AS tickets '
                    .'FROM ' . TICKET_TABLE . ' ticket '
                    .'WHERE ticket.status = \'closed\' '
    
            $stats = array();
    
                $stats[$row[0]] = $row[1];
    
            return $stats;
    
        /* Quick client's tickets stats
           @email - valid email.
    
         */
        function getClientStats($email) {