Skip to content
Snippets Groups Projects
class.ticket.php 82 KiB
Newer Older
  • Learn to ignore specific revisions
  •         if(!$email || !Validator::is_email($email))
                return null;
    
            if (!$user = User::lookup(array('emails__address'=>$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.user_id = '.db_input($user->getId());
    
    
            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 overrides config settings...
    
        function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) {
    
            global $ost, $cfg, $thisclient, $_FILES;
    
            // Don't enforce form validation for email
            $field_filter = function($f) use ($origin) {
                // Ultimately, only offer validation errors for web for
                // non-internal fields. For email, no validation can be
                // performed. For other origins, validate as usual
                switch (strtolower($origin)) {
                case 'email':
                    return false;
                case 'web':
                    return !$f->get('private');
                default:
                    return true;
                }
            };
    
            //Check for 403
            if ($vars['email']  && Validator::is_email($vars['email'])) {
    
                //Make sure the email address is not banned
    
                if(TicketFilter::isBanned($vars['email'])) {
    
                    $errors['err']='Ticket denied. Error #403';
    
                    $errors['errno'] = 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']));
    
            // Create and verify the dynamic form entry for the new ticket
    
            $form = TicketForm::getNewInstance();
    
            // If submitting via email, ensure we have a subject and such
            foreach ($form->getFields() as $field) {
                $fname = $field->get('name');
                if ($fname && isset($vars[$fname]) && !$field->value)
    
                    $field->value = $field->parse($vars[$fname]);
    
            if (!$form->isValid($field_filter))
    
                $errors += $form->errors();
    
            // Unpack dynamic variables into $vars for filter application
            foreach ($form->getFields() as $f)
                $vars['field.'.$f->get('id')] = $f->toString($f->getClean());
    
    
            // Unpack the basic user information
            $interesting = array('name', 'email');
            $user_form = UserForm::getUserForm()->getForm($vars);
            foreach ($user_form->getFields() as $f)
                if (in_array($f->get('name'), $interesting))
                    $vars[$f->get('name')] = $f->toString($f->getClean());
    
    
            //Init ticket filters...
            $ticket_filter = new TicketFilter($origin, $vars);
    
            // Make sure email contents should not be rejected
    
                    && ($filter=$ticket_filter->shouldReject())) {
    
                $errors['err']='Ticket denied. Error #403';
    
                $errors['errno'] = 403;
    
                $ost->logWarning('Ticket denied',
                        sprintf('Ticket rejected ( %s) by filter "%s"',
    
                            $vars['email'], $filter->getName()));
    
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            $id=0;
            $fields=array();
            $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';
    
    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 the due date is valid
    
            if($vars['duedate']) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                if(!$vars['time'] || strpos($vars['time'],':')===false)
                    $errors['time']='Select time';
                elseif(strtotime($vars['duedate'].' '.$vars['time'])===false)
    
                    $errors['duedate']='Invalid due date';
    
    Jared Hancock's avatar
    Jared Hancock committed
                elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time())
                    $errors['duedate']='Due date must be in the future';
            }
    
    
            if (!$errors) {
    
                if ($vars['uid'] && ($user = User::lookup($vars['uid']))) {
                    $vars['email'] = $user->getEmail();
                    $vars['name'] = $user->getName();
                }
    
                # Perform ticket filter actions on the new ticket arguments
                if ($ticket_filter) $ticket_filter->apply($vars);
    
                // Allow vars to be changed in ticket filter and applied to the user
                // account created or detected
                if (!$user) {
                    $user_form = UserForm::getUserForm()->getForm($vars);
                    if (!$user_form->isValid($field_filter)
    
    Peter Rotich's avatar
    Peter Rotich committed
                            || !($user=User::fromVars($user_form->getClean())))
    
                        $errors['user'] = 'Incomplete client information';
                }
    
            //Any error above is fatal.
            if($errors)  return 0;
    
    
    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'];
    
            // OK...just do it.
            $deptId=$vars['deptId']; //pre-selected Dept if any.
            $source=ucfirst($vars['source']);
            $topic=NULL;
    
            // Intenal mapping magic...see if we need to override anything
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(isset($vars['topicId']) && ($topic=Topic::lookup($vars['topicId']))) { //Ticket created via web by user/or staff
                $deptId=$deptId?$deptId:$topic->getDeptId();
    
                $priority = $form->getAnswer('priority');
                if (!$priority || !$priority->getIdValue())
    
                    $form->setAnswer('priority', null, $topic->getPriorityId());
    
    Jared Hancock's avatar
    Jared Hancock committed
                if($autorespond) $autorespond=$topic->autoRespond();
                $source=$vars['source']?$vars['source']:'Web';
    
                if (!isset($vars['staffId']) && $topic->getStaffId())
                    $vars['staffId'] = $topic->getStaffId();
                elseif (!isset($vars['teamId']) && $topic->getTeamId())
                    $vars['teamId'] = $topic->getTeamId();
    
                if(isset($vars['slaId']))
                    $vars['slaId'] = $vars['slaId']?$vars['slaId']:$cfg->getDefaultSLAId();
                elseif($topic && $topic->getSLAId())
    
                    $vars['slaId'] = $topic->getSLAId();
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            }elseif($vars['emailId'] && !$vars['deptId'] && ($email=Email::lookup($vars['emailId']))) { //Emailed Tickets
                $deptId=$email->getDeptId();
    
                $priority = $form->getAnswer('priority');
                if (!$priority || !$priority->getIdValue())
    
                    $form->setAnswer('priority', null, $email->getPriorityId());
    
    Jared Hancock's avatar
    Jared Hancock committed
                if($autorespond) $autorespond=$email->autoRespond();
                $email=null;
                $source='Email';
            }
            //Last minute checks
    
            $priority = $form->getAnswer('priority');
            if (!$priority || !$priority->getIdValue())
    
                $form->setAnswer('priority', null, $cfg->getDefaultPriorityId());
    
    Jared Hancock's avatar
    Jared Hancock committed
            $deptId=$deptId?$deptId:$cfg->getDefaultDeptId();
            $topicId=$vars['topicId']?$vars['topicId']:0;
            $ipaddress=$vars['ip']?$vars['ip']:$_SERVER['REMOTE_ADDR'];
    
    Jared Hancock's avatar
    Jared Hancock committed
            //We are ready son...hold on to the rails.
            $extId=Ticket::genExtRandID();
            $sql='INSERT INTO '.TICKET_TABLE.' SET created=NOW() '
                .' ,lastmessage= NOW()'
    
                .' ,user_id='.db_input($user->id)
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' ,ticketID='.db_input($extId)
                .' ,dept_id='.db_input($deptId)
                .' ,topic_id='.db_input($topicId)
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' ,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 ------------------------ */
    
    Jared Hancock's avatar
    Jared Hancock committed
                //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');
    
    Jared Hancock's avatar
    Jared Hancock committed
                //TODO: RETHING what happens if this fails?? [At the moment on failure random ID is used...making stuff usable]
    
            // Save the (common) dynamic form
            $form->setTicketId($id);
            $form->save();
    
            $ticket->loadDynamicData();
    
            $dept = $ticket->getDept();
    
    Jared Hancock's avatar
    Jared Hancock committed
            //post the message.
    
            unset($vars['cannedattachments']); //Ticket::open() might have it set as part of  open & respond.
            $vars['title'] = $vars['subject']; //Use the initial subject as title of the post.
            $message = $ticket->postMessage($vars , $origin, false);
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            // 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');
    
    Jared Hancock's avatar
    Jared Hancock committed
            if($vars['teamId'] && !$vars['assignId'])
    
                $ticket->assignToTeam($vars['teamId'], 'Auto Assignment');
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            /**********   double check auto-response  ************/
    
            //Override auto responder if the FROM email is one of the internal emails...loop control.
    
    Jared Hancock's avatar
    Jared Hancock committed
            if($autorespond && (Email::getIdByEmail($ticket->getEmail())))
                $autorespond=false;
    
            # Messages that are clearly auto-responses from email systems should
            # not have a return 'ping' message
    
            if ($autorespond && $message && $message->isAutoResponse())
    
    Jared Hancock's avatar
    Jared Hancock committed
                $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;
            }
    
    
            //post canned auto-response IF any (disables new ticket auto-response).
    
            if ($vars['cannedResponseId']
    
                && $ticket->postCannedReply($vars['cannedResponseId'], $message->getId(), $autorespond)) {
                    $ticket->markUnAnswered(); //Leave the ticket as unanswred.
    
                    $autorespond = false;
    
            //Check department's auto response settings
            // XXX: Dept. setting doesn't affect canned responses.
            if($autorespond && $dept && !$dept->autoRespONNewTicket())
                $autorespond=false;
    
    
            /***** See if we need to send some alerts ****/
    
            $ticket->onNewTicket($message, $autorespond, $alertstaff);
    
            /************ check if the user JUST reached the max. open tickets limit **********/
            if($cfg->getMaxOpenTickets()>0
                        && ($client=$ticket->getClient())
                        && ($client->getNumOpenTickets()==$cfg->getMaxOpenTickets())) {
                $ticket->onOpenLimit(($autorespond && strcasecmp($origin, 'staff')));
            }
    
            /* Start tracking ticket lifecycle events */
            $ticket->logEvent('created');
    
    
            /* Phew! ... time for tea (KETEPA) */
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $ticket;
        }
    
    
        function open($vars, &$errors) {
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if(!$thisstaff || !$thisstaff->canCreateTickets()) return false;
    
    
            if($vars['source'] && !in_array(strtolower($vars['source']),array('email','phone','other')))
                $errors['source']='Invalid source - '.Format::htmlchars($vars['source']);
    
    
            if (!$vars['uid']) {
                //Special validation required here
                if (!$vars['email'] || !Validator::is_email($vars['email']))
                    $errors['email'] = 'Valid email required';
    
                if (!$vars['name'])
                    $errors['name'] = 'Name required';
            }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(!($ticket=Ticket::create($vars, $errors, 'staff', false, (!$vars['assignId']))))
                return false;
    
            $vars['msgId']=$ticket->getLastMsgId();
    
    Jared Hancock's avatar
    Jared Hancock committed
            // post response - if any
    
            $response = null;
            if($vars['response'] && $thisstaff->canPostReply()) {
    
                $vars['response'] = $ticket->replaceVars($vars['response']);
    
                if(($response=$ticket->postReply($vars, $errors, false))) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                    //Only state supported is closed on response
                    if(isset($vars['ticket_state']) && $thisstaff->canCloseTickets())
                        $ticket->setState($vars['ticket_state']);
                }
            }
    
    Jared Hancock's avatar
    Jared Hancock committed
            //Post Internal note
    
    Jared Hancock's avatar
    Jared Hancock committed
            if($vars['assignId'] && $thisstaff->canAssignTickets()) { //Assign ticket to staff or team.
    
                $ticket->assign($vars['assignId'], $vars['note']);
    
    Jared Hancock's avatar
    Jared Hancock committed
            } elseif($vars['note']) { //Not assigned...save optional note if any
    
    Peter Rotich's avatar
    Peter Rotich committed
                $ticket->logNote('New Ticket', $vars['note'], $thisstaff, false);
    
    Jared Hancock's avatar
    Jared Hancock committed
            } else { //Not assignment and no internal note - log activity
                $ticket->logActivity('New Ticket by Staff','Ticket created by staff -'.$thisstaff->getName());
            }
    
            $ticket->reload();
    
            if(!$cfg->notifyONNewStaffTicket()
                    || !isset($vars['alertuser'])
                    || !($dept=$ticket->getDept()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                return $ticket; //No alerts.
    
            //Send Notice to user --- if requested AND enabled!!
    
            if(($tpl=$dept->getTemplate())
                    && ($msg=$tpl->getNewTicketNoticeMsgTemplate())
                    && ($email=$dept->getEmail())) {
    
                $message = $vars['message'];
    
                if($response) {
                    $message .= ($cfg->isHtmlThreadEnabled()) ? "<br><br>" : "\n\n";
                    $message .= $response->getBody();
                }
    
    Jared Hancock's avatar
    Jared Hancock committed
    
                if($vars['signature']=='mine')
                    $signature=$thisstaff->getSignature();
                elseif($vars['signature']=='dept' && $dept && $dept->isPublic())
                    $signature=$dept->getSignature();
                else
                    $signature='';
    
                $attachments =($cfg->emailAttachments() && $response)?$response->getAttachments():array();
    
    
                $msg = $ticket->replaceVars($msg->asArray(), array(
                    'message' => $message,
                    'signature' => $signature,
    
                    'response' => ($response) ? $response->getBody() : '',
    
    Jared Hancock's avatar
    Jared Hancock committed
    
                if($cfg->stripQuotedReply() && ($tag=trim($cfg->getReplySeparator())))
    
                    $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body'];
    
                $references = $ticket->getLastMessage()->getEmailMessageId();
                if (isset($response))
                    $references = array($response->getEmailMessageId(), $references);
                $options = array('references' => $references);
                $email->send($ticket->getEmail(), $msg['subj'], $msg['body'], $attachments,
                    $options);
    
    Jared Hancock's avatar
    Jared Hancock committed
            }
    
            return $ticket;
    
        function checkOverdue() {
    
            $sql='SELECT ticket_id FROM '.TICKET_TABLE.' T1 '
    
                .' LEFT JOIN '.SLA_TABLE.' T2 ON (T1.sla_id=T2.id AND T2.isactive=1) '
    
                .' WHERE status=\'open\' AND isoverdue=0 '
                .' AND ((reopened is NULL AND duedate is NULL AND TIME_TO_SEC(TIMEDIFF(NOW(),T1.created))>=T2.grace_period*3600) '
                .' OR (reopened is NOT NULL AND duedate is NULL AND TIME_TO_SEC(TIMEDIFF(NOW(),reopened))>=T2.grace_period*3600) '
                .' OR (duedate is NOT NULL AND duedate<NOW()) '
                .' ) ORDER BY T1.created LIMIT 50'; //Age upto 50 tickets at a time?
    
    Jared Hancock's avatar
    Jared Hancock committed
            //echo $sql;
    
            if(($res=db_query($sql)) && db_num_rows($res)) {
                while(list($id)=db_fetch_row($res)) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                    if(($ticket=Ticket::lookup($id)) && $ticket->markOverdue())
    
                        $ticket->logActivity('Ticket Marked Overdue', 'Ticket flagged as overdue by the system.');
    
            } else {
                //TODO: Trigger escalation on already overdue tickets - make sure last overdue event > grace_period.