Skip to content
Snippets Groups Projects
class.ticket.php 74.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jared Hancock's avatar
    Jared Hancock committed
            if(!($email=$cfg->getAlertEmail()))
                $email =$cfg->getDefaultEmail();
    
            //Get the message template
            if($tpl && ($msg=$tpl->getAssignedAlertMsgTemplate()) && $email) {
    
                $body=$this->replaceTemplateVars($msg['body']);
                $subj=$this->replaceTemplateVars($msg['subj']);
                $body = str_replace('%note', $note, $body);
                $body = str_replace('%message', $note, $body); //Previous versions used message.
                $body = str_replace('%assignee', $this->getAssignee(), $body);
                $body = str_replace('%assigner', ($thisstaff)?$thisstaff->getName():'System',$body);
                //recipients
                $recipients=array();
                //Assigned staff or team... if any
                // Assigning a ticket to a team when already assigned to staff disables alerts to the team (!))
    
                if($cfg->alertStaffONAssignment() && $this->getStaffId())
    
    Jared Hancock's avatar
    Jared Hancock committed
                    $recipients[]=$this->getStaff();
                elseif($this->getTeamId() && ($team=$this->getTeam())) {
    
                    if($cfg->alertTeamMembersONAssignment() && ($members=$team->getMembers()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                        $recipients+=$members;
    
                    elseif($cfg->alertTeamLeadONAssignment() && ($lead=$team->getTeamLead()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                        $recipients[]=$lead;
                }
                //Send the alerts.
                $sentlist=array();
                foreach( $recipients as $k=>$staff){
                    if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue;
                    $alert = str_replace('%staff', $staff->getFirstName(), $body);
                    $email->send($staff->getEmail(), $subj, $alert);
                }
                print_r($sentlist);
            }
    
            return true;
        }
    
    
    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())
                return true;
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            $dept = $this->getDept();
            //Get department-defined or default template.
            if(!$dept || !($tpl = $dept->getTemplate()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                $tpl= $cfg->getDefaultTemplate();
    
            //Email to use!
            if(!($email=$cfg->getAlertEmail()))
                $email =$cfg->getDefaultEmail();
    
            //Get the message template
            if($tpl && ($msg=$tpl->getOverdueAlertMsgTemplate()) && $email) {
    
                $body=$this->replaceTemplateVars($msg['body']);
                $subj=$this->replaceTemplateVars($msg['subj']);
                $body = str_replace('%comments', $comments, $body); //Planned support.
    
                //recipients
                $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=array_merge($recipients, $members);
                } elseif($cfg->alertDeptMembersONOverdueTicket() && !$this->isAssigned()) {
                    //Only alerts dept members if the ticket is NOT assigned.
                    if(($members=$dept->getAvailableMembers()))
                        $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 = str_replace("%staff",$staff->getFirstName(),$body);
                    $email->send($staff->getEmail(),$subj,$alert);
                }
    
            }
    
            return true;
        }
    
        //Replace base variables.
        function replaceTemplateVars($text){
            global $cfg;
    
            $dept = $this->getDept();
            $staff= $this->getStaff();
            $team = $this->getTeam();
    
            //TODO: add new vars (team, sla...etc)
    
    
            $search = array('/%id/','/%ticket/','/%email/','/%name/','/%subject/','/%topic/','/%phone/','/%status/','/%priority/',
                            '/%dept/','/%assigned_staff/','/%createdate/','/%duedate/','/%closedate/','/%url/');
            $replace = array($this->getId(),
                             $this->getExtId(),
                             $this->getEmail(),
                             $this->getName(),
                             $this->getSubject(),
                             $this->getHelpTopic(),
                             $this->getPhoneNumber(),
                             $this->getStatus(),
                             $this->getPriority(),
                             ($dept?$dept->getName():''),
                             ($staff?$staff->getName():''),
                             Format::db_daydatetime($this->getCreateDate()),
                             Format::db_daydatetime($this->getDueDate()),
                             Format::db_daydatetime($this->getCloseDate()),
                             $cfg->getBaseUrl());
            return preg_replace($search,$replace,$text);
        }
    
    
    
    
        function markUnAnswered() {
            return (!$this->isAnswered() || $this->setAnsweredState(0));
        }
    
        function markAnswered() {
            return ($this->isAnswered() || $this->setAnsweredState(1));
        }
    
        function markOverdue($whine=true) {
            
            global $cfg;
    
            
            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;
        }
    
        //Dept Tranfer...with alert.. done by staff 
        function transfer($deptId, $comments, $alert = true) {
            global $cfg, $thisstaff;
          
            if(!$this->setDeptId($deptId))
                return false;
             
            // Change to SLA of the new department
            $this->selectSLAId();
             $currentDept = $this->getDeptName(); //XXX: add to olddept to tpl vars??
    
             // Reopen ticket if closed 
             if($this->isClosed())
                 $this->reopen();
            
             $this->reload(); //reload - new dept!!
    
             $this->logEvent('transferred');
    
    Jared Hancock's avatar
    Jared Hancock committed
    
             //Send out alerts if enabled AND requested
             if(!$alert || !$cfg->alertONTransfer() || !($dept=$this->getDept())) return true; //no alerts!!
    
    
             //Get template.
             if(!($tpl = $dept->getTemplate()))
                 $tpl= $cfg->getDefaultTemplate();
            
             //Email to use!
             if(!($email=$cfg->getAlertEmail()))
                 $email =$cfg->getDefaultEmail();
                    
             //Get the message template 
             if($tpl && ($msg=$tpl->getTransferAlertMsgTemplate()) && $email) {
                
                 $body=$this->replaceTemplateVars($msg['body']);
                 $subj=$this->replaceTemplateVars($msg['subj']);
                 $body = str_replace('%note', $comments, $body);
                            
                //recipients            
                $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.
                    if(($members=$dept->getAvailableMembers()))
                        $recipients+=$members;
                }
    
                //Always alert dept manager??
                if($cfg->alertDeptManagerONTransfer() && $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 = str_replace("%staff",$staff->getFirstName(),$body);
                    $email->send($staff->getEmail(),$subj,$alert);
                }
             }
    
             return true;
        }
    
        function assignToStaff($staff, $note, $alert=true) {
    
            if(!is_object($staff) && !($staff=Staff::lookup($staff)))
                return false;
            
            if(!$this->setStaffId($staff->getId()))
                return false;
    
            $this->onAssign($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($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);
            if($assignId[0]=='t') {
                $rv=$this->assignToTeam($id, $note, $alert);
            } elseif($assignId[0]=='s' || is_numeric($assignId)) {
                $alert=($thisstaff && $thisstaff->getId()==$id)?false:$alert; //No alerts on self assigned tickets!!!
                //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;
        }
        
        //unassign primary assignee
        function unassign() {
    
            if(!$this->isAssigned()) //We can't release what is not assigned buddy!
                return true;
    
            //We're unassigning in the order of precedence.
            if($this->getStaffId())
                return $this->setStaffId(0);
            elseif($this->getTeamId())
                return $this->setTeamId(0);
    
            return false;
        }
    
        function release() {
            return $this->unassign();
        }
    
        //Insert message from client
    
        function postMessage($msg,$source='',$emsgid=null,$headers='',$newticket=false){
    
    Jared Hancock's avatar
    Jared Hancock committed
            global $cfg;
           
            if(!$this->getId()) return 0;
            
            # XXX: Refuse auto-response messages? (via email) XXX: No - but kill our auto-responder.
    
    
            $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW()'
                .' ,thread_type="M" '
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' ,ticket_id='.db_input($this->getId())
    
                # XXX: Put Subject header into the 'title' field
                .' ,body='.db_input(Format::striptags($msg)) //Tags/code stripped...meaning client can not send in code..etc
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' ,source='.db_input($source?$source:$_SERVER['REMOTE_ADDR'])
                .' ,ip_address='.db_input($_SERVER['REMOTE_ADDR']);
        
            if(!db_query($sql) || !($msgid=db_insert_id())) return 0; //bail out....
    
            $this->setLastMsgId($msgid);
    
    
            if ($emsgid !== null) {
                $sql='INSERT INTO '.TICKET_EMAIL_INFO_TABLE
    
                    .' SET message_id='.db_input($msgid)
    
                    .', email_mid='.db_input($emsgid)
                    .', headers='.db_input($headers);
    
                if (!db_query($sql)) return 0;
            }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            if($newticket) return $msgid; //Our work is done...
    
            $autorespond = true;
            if ($autorespond && $headers && EmailFilter::isAutoResponse(Mail_Parse::splitHeaders($headers)))
                $autorespond=false;
    
            $this->onMessage($autorespond); //must be called b4 sending alerts to staff.
    
    
            $dept = $this->getDept();
    
            if(!$dept || !($tpl = $dept->getTemplate()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                $tpl= $cfg->getDefaultTemplate();
    
            if(!($email=$cfg->getAlertEmail()))
                $email =$cfg->getDefaultEmail();
    
    
            //If enabled...send alert to staff (New Message Alert)
            if($cfg->alertONNewMessage() && $tpl && $email && ($msg=$tpl->getNewMessageAlertMsgTemplate())) {
    
                $body=$this->replaceTemplateVars($msg['body']);
                $subj=$this->replaceTemplateVars($msg['subj']);
                $body = str_replace("%message", $msg,$body);
    
                //Build list of recipients and fire the alerts.
                $recipients=array();
                //Last respondent.
                if($cfg->alertLastRespondentONNewMessage() || $cfg->alertAssignedONNewMessage())
                    $recipients[]=$this->getLastRespondent();
                    
                //Assigned staff if any...could be the last respondent
                
                if($this->isAssigned() && ($staff=$this->getStaff()))
                    $recipients[]=$staff;
                    
                //Dept manager
                if($cfg->alertDeptManagerONNewMessage() && $dept && ($manager=$dept->getManager()))
                    $recipients[]=$manager;
                    
                $sentlist=array(); //I know it sucks...but..it works.
                foreach( $recipients as $k=>$staff){
                    if(!$staff || !$staff->getEmail() || !$staff->isAvailable() && in_array($staff->getEmail(),$sentlist)) continue;
                    $alert = str_replace("%staff",$staff->getFirstName(),$body);
                    $email->send($staff->getEmail(),$subj,$alert);
                    $sentlist[]=$staff->getEmail();
                }
            }
            
            return $msgid;
        }
    
        /* public */ 
        function postReply($vars, $files, $errors, $alert = true) {
            global $thisstaff,$cfg;
    
            if(!$thisstaff || !$thisstaff->isStaff() || !$cfg) return 0;
    
            if(!$vars['msgId'])
                $errors['msgId'] ='Missing messageId - internal error';
            if(!$vars['response'])
                $errors['response'] = 'Resonse message required';
    
            if($errors) return 0;
    
    
            $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() '
                .' ,thread_type="R"'
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' ,ticket_id='.db_input($this->getId())
    
                .' ,pid='.db_input($vars['msgId'])
                .' ,body='.db_input(Format::striptags($vars['response']))
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' ,staff_id='.db_input($thisstaff->getId())
    
                .' ,poster='.db_input($thisstaff->getName())
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' ,ip_address='.db_input($thisstaff->getIP());
    
            if(!db_query($sql) || !($respId=db_insert_id()))
                return false;
    
            //Set status - if checked.
            if(isset($vars['reply_ticket_status']) && $vars['reply_ticket_status'])
                $this->setStatus($vars['reply_ticket_status']);
    
            /* We can NOT recover from attachment related failures at this point */
            //upload files.
            $attachments = $uploads = array();
            //Web based upload..
            if($files && is_array($files) && ($files=Format::files($files)))
                $attachments=array_merge($attachments,$files);
    
            //Canned attachments...
            if($vars['cannedattachments'] && is_array($vars['cannedattachments']))
                $attachments=array_merge($attachments,$vars['cannedattachments']);
    
            
            //Upload attachments -ids used on outgoing emails are returned.
            if($attachments)
                $uploads = $this->uploadAttachments($attachments, $respId,'R');
    
            $this->onResponse(); //do house cleaning..
            $this->reload();
    
            /* email the user??  - if disabled - the bail out */
            if(!$alert) return $respId;
    
    
            $dept = $this->getDept();
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(!($tpl = $dept->getTemplate()))
                $tpl= $cfg->getDefaultTemplate();
    
    
            if(!$dept || !($email=$dept->getEmail()))
                $email = $cfg->getDefaultEmail();
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if($tpl && ($msg=$tpl->getReplyMsgTemplate()) && $email) {
                $body=$this->replaceTemplateVars($msg['body']);
                $subj=$this->replaceTemplateVars($msg['subj']);
                $body = str_replace('%response',$vars['response'],$body);
    
                if($vars['signature']=='mine')
                    $signature=$thisstaff->getSignature();
                elseif($vars['signature']=='dept' && $dept && $dept->isPublic())
                    $signature=$dept->getSignature();
                else
                    $signature='';
    
                $body = str_replace("%signature",$signature,$body);
    
                if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
                    $body ="\n$tag\n\n".$body;
    
                //Set attachments if emailing.
                $attachments =($cfg->emailAttachments() && $uploads)?$this->getAttachments($respId,'R'):array();
                //TODO: setup  5 param (options... e.g mid trackable on replies)
                $email->send($this->getEmail(), $subj, $body, $attachments);
            }
    
            return $respId;
        }
    
        //Activity log - saved as internal notes WHEN enabled!!
        function logActivity($title,$note){
            global $cfg;
    
            if(!$cfg || !$cfg->logTicketActivity())
                return 0;
    
    
            return $this->postNote($title,$note,false,'System');
    
        // 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;
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        //Insert Internal Notes 
        function postNote($title,$note,$alert=true,$poster='') {        
            global $thisstaff,$cfg;
    
    
            $sql= 'INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() '.
                    ',thread_type="N"'.
    
    Jared Hancock's avatar
    Jared Hancock committed
                    ',ticket_id='.db_input($this->getId()).
                    ',title='.db_input(Format::striptags($title)).
    
                    ',body='.db_input(Format::striptags($note)).
    
    Jared Hancock's avatar
    Jared Hancock committed
                    ',staff_id='.db_input($thisstaff?$thisstaff->getId():0).
    
                    ',poster='.db_input(($poster || !$thisstaff)?$poster:$thisstaff->getName());
    
    Jared Hancock's avatar
    Jared Hancock committed
            //echo $sql;
            if(!db_query($sql) || !($id=db_insert_id()))
                return false;
    
            // If alerts are not enabled then return a success.
            if(!$alert || !$cfg->alertONNewNote() || !($dept=$this->getDept()))
                return $id;
            
            if(!($tpl = $dept->getTemplate()))
                $tpl= $cfg->getDefaultTemplate();
    
            if(!($email=$cfg->getAlertEmail()))
                $email =$cfg->getDefaultEmail();
    
    
            if($tpl && ($msg=$tpl->getNoteAlertMsgTemplate()) && $email) {
                        
                $body=$this->replaceTemplateVars($msg['body']);
                $subj=$this->replaceTemplateVars($msg['subj']);
                $body = str_replace('%note',"$title\n\n$note",$body);
    
                // Alert recipients    
                $recipients=array();
                
                //Last respondent.
                if($cfg->alertLastRespondentONNewNote())
                    $recipients[]=$this->getLastRespondent();
                
                //Assigned staff if any...could be the last respondent
                if($cfg->alertAssignedONNewNote() && $this->isAssigned() && $this->getStaffId())
                    $recipients[]=$this->getStaff();
                    
                //Dept manager
                if($cfg->alertDeptManagerONNewNote() && $dept && $dept->getManagerId())
                    $recipients[]=$dept->getManager();
    
                $sentlist=array();
                foreach( $recipients as $k=>$staff) {
                    if(!$staff || !is_object($staff) || !$staff->getEmail() || !$staff->isAvailable()) continue;
                    if(in_array($staff->getEmail(),$sentlist) || ($thisstaff && $thisstaff->getId()==$staff->getId())) continue; 
                    $alert = str_replace('%staff',$staff->getFirstName(),$body);
                    $email->send($staff->getEmail(),$subj,$alert);
                    $sentlist[]=$staff->getEmail();
                }
            }
            
            return $id;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        //Print ticket... export the ticket thread as PDF.
        function exportPDF() {
            return false;
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        //online based attached files.
        function uploadAttachments($files, $refid, $type) {
    
            $uploaded=array();
            foreach($files as $file) {
                if(($fileId=is_numeric($file)?$file:AttachmentFile::upload($file)) && is_numeric($fileId))
                    if($this->saveAttachment($fileId, $refid, $type))
                        $uploaded[]=$fileId;
            }
    
            return $uploaded;
        }
    
        /*
           Save attachment to the DB. uploads (above), email or json/xml.
           
           @file is a mixed var - can be ID or file hash.
         */
        function saveAttachment($file, $refid, $type) {
    
            if(!$refid || !$type || !($fileId=is_numeric($file)?$file:AttachmentFile::save($file)))
                return 0;
    
            $sql ='INSERT INTO '.TICKET_ATTACHMENT_TABLE.' SET created=NOW() '
                 .' ,ticket_id='.db_input($this->getId())
                 .' ,file_id='.db_input($fileId)
                 .' ,ref_id='.db_input($refid)
                 .' ,ref_type='.db_input($type);
    
            return (db_query($sql) && ($id=db_insert_id()))?$id:0;
        }
        
    
    
        function deleteAttachments(){
    
    Peter Rotich's avatar
    Peter Rotich committed
            
    
    Jared Hancock's avatar
    Jared Hancock committed
            $deleted=0;
    
            // Clear reference table
            $res=db_query('DELETE FROM '.TICKET_ATTACHMENT_TABLE.' WHERE ticket_id='.db_input($this->getId()));
            if ($res && db_affected_rows())
                $deleted = AttachmentFile::deleteOrphans();
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            return $deleted;
        }
    
    
        function delete(){
            
    
    Peter Rotich's avatar
    Peter Rotich committed
            $sql='DELETE FROM '.TICKET_TABLE.' WHERE ticket_id='.$this->getId().' LIMIT 1';
            if(!db_query($sql) || !db_affected_rows())
                return false;
    
    
            db_query('DELETE FROM '.TICKET_THREAD_TABLE.' WHERE ticket_id='.db_input($this->getId()));
    
    Peter Rotich's avatar
    Peter Rotich committed
            $this->deleteAttachments();
            
            return true;
        }
    
        function update($vars, &$errors) {
    
            global $cfg, $thisstaff;
            
            if(!$cfg || !$thisstaff || !$thisstaff->canEditTickets())
                return false;
             
            $fields=array();
            $fields['name']     = array('type'=>'string',   'required'=>1, 'error'=>'Name required');
            $fields['email']    = array('type'=>'email',    'required'=>1, 'error'=>'Valid email required');
            $fields['subject']  = array('type'=>'string',   'required'=>1, 'error'=>'Subject required');
            $fields['topicId']  = array('type'=>'int',      'required'=>1, 'error'=>'Help topic required');
            $fields['slaId']    = array('type'=>'int',      'required'=>1, 'error'=>'SLA required');
            $fields['priorityId'] = array('type'=>'int',    'required'=>1, 'error'=>'Priority required');
            $fields['phone']    = array('type'=>'phone',    'required'=>0, 'error'=>'Valid phone # required');
            $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');
    
            if(!Validator::process($fields, $vars, $errors) && !$errors['err'])
                $errors['err'] ='Missing or invalid data - check the errors and try again';
    
            if($vars['duedate']) {     
                if($this->isClosed())
                    $errors['duedate']='Duedate can NOT be set on a closed ticket';
                elseif(!$vars['time'] || strpos($vars['time'],':')===false)
                    $errors['time']='Select time';
                elseif(strtotime($vars['duedate'].' '.$vars['time'])===false)
                    $errors['duedate']='Invalid duedate';
                elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time())
                    $errors['duedate']='Due date must be in the future';
            }
            
            //Make sure phone extension is valid
            if($vars['phone_ext'] ) {
                if(!is_numeric($vars['phone_ext']) && !$errors['phone'])
                    $errors['phone']='Invalid phone ext.';
                elseif(!$vars['phone']) //make sure they just didn't enter ext without phone #
                    $errors['phone']='Phone number required';
            }
    
            if($errors) return false;
    
            $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW() '
                .' ,email='.db_input($vars['email'])
                .' ,name='.db_input(Format::striptags($vars['name']))
                .' ,subject='.db_input(Format::striptags($vars['subject']))
                .' ,phone="'.db_input($vars['phone'],false).'"'
                .' ,phone_ext='.db_input($vars['phone_ext']?$vars['phone_ext']:NULL)
                .' ,priority_id='.db_input($vars['priorityId'])
                .' ,topic_id='.db_input($vars['topicId'])
                .' ,sla_id='.db_input($vars['slaId'])
                .' ,duedate='.($vars['duedate']?db_input(date('Y-m-d G:i',Misc::dbtime($vars['duedate'].' '.$vars['time']))):'NULL');
                 
            if($vars['duedate']) { //We are setting new duedate...
                $sql.=' ,isoverdue=0';
            }
                 
            $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());
    
            $this->postNote('Ticket Updated', $vars['note']);
            $this->reload();
    
    Peter Rotich's avatar
    Peter Rotich committed
            return true;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
    Jared Hancock's avatar
    Jared Hancock committed
       
       /*============== Static functions. Use Ticket::function(params); ==================*/
        function getIdByExtId($extid) {
            $sql ='SELECT  ticket_id FROM '.TICKET_TABLE.' ticket WHERE ticketID='.db_input($extid);
            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!
    
    Jared Hancock's avatar
    Jared Hancock committed
            return ($id && is_numeric($id) && ($ticket= new Ticket($id)) && $ticket->getId()==$id)?$ticket:null;    
        }
    
    
        function lookupByExtId($id) {
            return self::lookup(self:: getIdByExtId($id));
        }
    
    
    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) {
    
            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;
        }
    
        function getOpenTicketsByEmail($email){
    
            $sql='SELECT count(*) as open FROM '.TICKET_TABLE.' WHERE status='.db_input('open').' AND email='.db_input($email);
            if(($res=db_query($sql)) && db_num_rows($res))
                list($num)=db_fetch_row($res);
    
            return $num;
        }
    
        /* Quick staff's tickets stats */ 
        function getStaffStats($staff) {
            global $cfg;
            
            /* Unknown or invalid staff */
            if(!$staff || (!is_object($staff) && !($staff=Staff::lookup($staff))) || !$staff->isStaff())
                return null;
    
    
            $sql='SELECT count(open.ticket_id) as open, count(answered.ticket_id) as answered '
                .' ,count(overdue.ticket_id) as overdue, count(assigned.ticket_id) as assigned, count(closed.ticket_id) as closed '
                .' FROM '.TICKET_TABLE.' ticket '
                .' LEFT JOIN '.TICKET_TABLE.' open
                    ON (open.ticket_id=ticket.ticket_id AND open.status=\'open\' AND open.isanswered=0) '
                .' LEFT JOIN '.TICKET_TABLE.' answered
                    ON (answered.ticket_id=ticket.ticket_id AND answered.status=\'open\' AND answered.isanswered=1) '
                .' LEFT JOIN '.TICKET_TABLE.' overdue
                    ON (overdue.ticket_id=ticket.ticket_id AND overdue.status=\'open\' AND overdue.isoverdue=1) '
                .' LEFT JOIN '.TICKET_TABLE.' assigned
                    ON (assigned.ticket_id=ticket.ticket_id AND assigned.status=\'open\' AND assigned.staff_id='.db_input($staff->getId()).')'
                .' LEFT JOIN '.TICKET_TABLE.' closed
                    ON (closed.ticket_id=ticket.ticket_id AND closed.status=\'closed\' AND closed.staff_id='.db_input($staff->getId()).')'
    
                .' WHERE (ticket.staff_id='.db_input($staff->getId());
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(($teams=$staff->getTeams()))
                $sql.=' OR ticket.team_id IN('.implode(',', array_filter($teams)).')';
    
    
            if(!$staff->showAssignedOnly()) //Staff with limited access just see Assigned tickets.
                $sql.=' OR ticket.dept_id IN('.implode(',',$staff->getDepts()).') ';
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            $sql.=')';
    
    
            if(!$cfg || !($cfg->showAssignedTickets() || $staff->showAssignedTickets()))
                $sql.=' AND (ticket.staff_id=0 OR ticket.staff_id='.db_input($staff->getId()).') ';
    
         
            return db_fetch_array(db_query($sql));
        }
    
    
    
        /* Quick client's tickets stats 
           @email - valid email. 
         */
        function getClientStats($email) {
    
            if(!$email || !Validator::is_email($email))
                return null;
    
            $sql='SELECT count(open.ticket_id) as open, count(closed.ticket_id) as closed '
                .' FROM '.TICKET_TABLE.' ticket '
                .' LEFT JOIN '.TICKET_TABLE.' open
                    ON (open.ticket_id=ticket.ticket_id AND open.status=\'open\') '
                .' LEFT JOIN '.TICKET_TABLE.' closed
                    ON (closed.ticket_id=ticket.ticket_id AND closed.status=\'closed\')'
                .' WHERE ticket.email='.db_input($email);
    
            return db_fetch_array(db_query($sql));
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        /*
         * The mother of all functions...You break it you fix it!
         *
    
         *  $autorespond and $alertstaff overwrites config settings...
    
        function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) {
    
            global $ost, $cfg, $thisclient, $_FILES;
    
            //Check for 403
            if ($vars['email']  && Validator::is_email($vars['email'])) {
    
                //Make sure the email address is not banned
                if(EmailFilter::isBanned($vars['email'])) {
                    $errors['err']='Ticket denied. Error #403';
    
                    $ost->logWarning('Ticket denied', 'Banned email - '.$vars['email']);
    
                    return 0;
                }
    
                //Make sure the open ticket limit hasn't been reached. (LOOP CONTROL)
                if($cfg->getMaxOpenTickets()>0 && strcasecmp($origin,'staff') 
                        && ($client=Client::lookupByEmail($vars['email']))
                        && ($openTickets=$client->getNumOpenTickets())
    
    Jared Hancock's avatar
    Jared Hancock committed
                        && ($openTickets>=$cfg->getMaxOpenTickets()) ) {
    
    
                    $errors['err']="You've reached the maximum open tickets allowed.";
    
                    $ost->logWarning('Ticket denied -'.$vars['email'], 
                            sprintf('Max open tickets (%d) reached for %s ', 
                                $cfg->getMaxOpenTickets(), $vars['email']));
    
            }
            // Make sure email contents should not be rejected
            if (($email_filter=new EmailFilter($vars))
                    && ($filter=$email_filter->shouldReject())) {
                $errors['err']='Ticket denied. Error #403';
    
                $ost->logWarning('Ticket denied', 
                        sprintf('Banned email - %s by filter "%s"', 
                            $vars['email'], $filter->getName()));
    
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            $id=0;
            $fields=array();
            $fields['name']     = array('type'=>'string',   'required'=>1, 'error'=>'Name required');
            $fields['email']    = array('type'=>'email',    'required'=>1, 'error'=>'Valid email required');
            $fields['subject']  = array('type'=>'string',   'required'=>1, 'error'=>'Subject required');
            $fields['message']  = array('type'=>'text',     'required'=>1, 'error'=>'Message required');
            switch (strtolower($origin)) {
                case 'web':
                    $fields['topicId']  = array('type'=>'int',  'required'=>1, 'error'=>'Select help topic');
                    break;
                case 'staff':
                    $fields['deptId']   = array('type'=>'int',  'required'=>1, 'error'=>'Dept. required');
    
                    $fields['topicId']  = array('type'=>'int',  'required'=>1, 'error'=>'Topic required');
    
    Jared Hancock's avatar
    Jared Hancock committed
                    $fields['duedate']  = array('type'=>'date', 'required'=>0, 'error'=>'Invalid date - must be MM/DD/YY');
                case 'api':
                    $fields['source']   = array('type'=>'string', 'required'=>1, 'error'=>'Indicate source');
                    break;
                case 'email':
                    $fields['emailId']  = array('type'=>'int',  'required'=>1, 'error'=>'Email unknown');
                    break;
                default:
                    # TODO: Return error message
    
                    $errors['err']=$errors['origin'] = 'Invalid origin given';
    
            $fields['priorityId']   = array('type'=>'int',      'required'=>0, 'error'=>'Invalid Priority');
            $fields['phone']        = array('type'=>'phone',    'required'=>0, 'error'=>'Valid phone # required');
    
    Jared Hancock's avatar
    Jared Hancock committed
            
            if(!Validator::process($fields, $vars, $errors) && !$errors['err'])
                $errors['err'] ='Missing or invalid data - check the errors and try again';
    
            //Make sure phone extension is valid
            if($vars['phone_ext'] ) {
                if(!is_numeric($vars['phone_ext']) && !$errors['phone'])
                    $errors['phone']='Invalid phone ext.';
                elseif(!$vars['phone']) //make sure they just didn't enter ext without phone # XXX: reconsider allowing!
                    $errors['phone']='Phone number required';
            }
    
            //Make sure the due date is valid
            if($vars['duedate']){
                if(!$vars['time'] || strpos($vars['time'],':')===false)
                    $errors['time']='Select time';
                elseif(strtotime($vars['duedate'].' '.$vars['time'])===false)
                    $errors['duedate']='Invalid duedate';
                elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time())
                    $errors['duedate']='Due date must be in the future';
            }
    
            # Perform email filter actions on the new ticket arguments XXX: Move filter to the top and check for reject...
    
            if (!$errors && $email_filter) $email_filter->apply($vars);
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            # Some things will need to be unpacked back into the scope of this
            # function
            if (isset($vars['autorespond'])) $autorespond=$vars['autorespond'];
    
            //Any error above is fatal.
            if($errors)  return 0;
            
            // OK...just do it.
            $deptId=$vars['deptId']; //pre-selected Dept if any.
    
    Peter Rotich's avatar
    Peter Rotich committed
            $priorityId=$vars['priorityId'];
    
    Jared Hancock's avatar
    Jared Hancock committed
            $source=ucfirst($vars['source']);
            $topic=NULL;
            // Intenal mapping magic...see if we need to overwrite anything
            if(isset($vars['topicId']) && ($topic=Topic::lookup($vars['topicId']))) { //Ticket created via web by user/or staff
                $deptId=$deptId?$deptId:$topic->getDeptId();
                $priorityId=$priorityId?$priorityId:$topic->getPriorityId();
                if($autorespond) $autorespond=$topic->autoRespond();
                $source=$vars['source']?$vars['source']:'Web';
            }elseif($vars['emailId'] && !$vars['deptId'] && ($email=Email::lookup($vars['emailId']))) { //Emailed Tickets
                $deptId=$email->getDeptId();
                $priorityId=$priorityId?$priorityId:$email->getPriorityId();
                if($autorespond) $autorespond=$email->autoRespond();
                $email=null;
                $source='Email';
            }elseif($vars['deptId']){ //Opened by staff.
                $deptId=$vars['deptId'];
                $source=ucfirst($vars['source']);
            }
    
            //Last minute checks
            $priorityId=$priorityId?$priorityId:$cfg->getDefaultPriorityId();
            $deptId=$deptId?$deptId:$cfg->getDefaultDeptId();
            $topicId=$vars['topicId']?$vars['topicId']:0;
            $ipaddress=$vars['ip']?$vars['ip']:$_SERVER['REMOTE_ADDR'];
            
            //We are ready son...hold on to the rails.
            $extId=Ticket::genExtRandID();
            $sql='INSERT INTO '.TICKET_TABLE.' SET created=NOW() '
                .' ,lastmessage= NOW()'
                .' ,ticketID='.db_input($extId)
                .' ,dept_id='.db_input($deptId)
                .' ,topic_id='.db_input($topicId)
                .' ,priority_id='.db_input($priorityId)
                .' ,email='.db_input($vars['email'])
                .' ,name='.db_input(Format::striptags($vars['name']))
                .' ,subject='.db_input(Format::striptags($vars['subject']))
                .' ,phone="'.db_input($vars['phone'],false).'"'
                .' ,phone_ext='.db_input($vars['phone_ext']?$vars['phone_ext']:'')
                .' ,ip_address='.db_input($ipaddress) 
                .' ,source='.db_input($source);
    
            //Make sure the origin is staff - avoid firebug hack!
            if($vars['duedate'] && !strcasecmp($origin,'staff'))
                 $sql.=' ,duedate='.db_input(date('Y-m-d G:i',Misc::dbtime($vars['duedate'].' '.$vars['time'])));
    
    
            if(!db_query($sql) || !($id=db_insert_id()) || !($ticket =Ticket::lookup($id)))
                return null;
    
            /* -------------------- POST CREATE ------------------------ */
            $dept = $ticket->getDept();
         
            if(!$cfg->useRandomIds()){
                //Sequential ticketIDs support really..really suck arse.
                $extId=$id; //To make things really easy we are going to use autoincrement ticket_id.
                db_query('UPDATE '.TICKET_TABLE.' SET ticketID='.db_input($extId).' WHERE ticket_id='.$id.' LIMIT 1'); 
                //TODO: RETHING what happens if this fails?? [At the moment on failure random ID is used...making stuff usable]
            }   
    
    
            //post the message.
            $msgid=$ticket->postMessage($vars['message'],$source,$vars['mid'],$vars['header'],true);
    
            // Configure service-level-agreement for this ticket
            $ticket->selectSLAId($vars['slaId']);
    
            //Auto assign staff or team - auto assignment based on filter rules.
            if($vars['staffId'] && !$vars['assignId'])
                 $ticket->assignToStaff($vars['staffId'],'auto-assignment');
            if($vars['teamId'] && !$vars['assignId'])
                $ticket->assignToTeam($vars['teamId'],'auto-assignment');
    
            /**********   double check auto-response  ************/
            //Overwrite auto responder if the FROM email is one of the internal emails...loop control.
            if($autorespond && (Email::getIdByEmail($ticket->getEmail())))
                $autorespond=false;
    
            if($autorespond && $dept && !$dept->autoRespONNewTicket())
                $autorespond=false;
    
            # Messages that are clearly auto-responses from email systems should
            # not have a return 'ping' message
            if ($autorespond && $vars['header'] &&
                    EmailFilter::isAutoResponse(Mail_Parse::splitHeaders($vars['header']))) {
                $autorespond=false;
            }
    
            //Don't auto respond to mailer daemons.
            if( $autorespond &&
                (strpos(strtolower($vars['email']),'mailer-daemon@')!==false
                 || strpos(strtolower($vars['email']),'postmaster@')!==false)) {
                $autorespond=false;