Newer
Older
//Get the message template
if($tpl && ($msg=$tpl->getOverdueAlertMsgTemplate()) && $email) {
Peter Rotich
committed
$msg = $this->replaceVars($msg->asArray(),
array('comments' => $comments));
//recipients
$recipients=array();
//Assigned staff or team... if any
if($this->isAssigned() && $cfg->alertAssignedONOverdueTicket()) {
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.
$recipients=array_merge($recipients, $members);
}
//Always alert dept manager??
if($cfg->alertDeptManagerONOverdueTicket() && $dept && ($manager=$dept->getManager()))
$recipients[]= $manager;
$sentlist=array();
Peter Rotich
committed
foreach( $recipients as $k=>$staff) {
if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
$alert = str_replace("%{recipient}", $staff->getFirstName(), $msg['body']);
$email->sendAlert($staff->getEmail(), $msg['subj'], $alert,
null);
Peter Rotich
committed
Peter Rotich
committed
//ticket obj as variable = ticket number.
function asVar() {
return $this->getNumber();
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(
Peter Rotich
committed
$cfg->getDateTimeFormat(),
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;
default:
if (isset($this->_answers[$tag]))
# TODO: Use DynamicField to format the value, also,
# private field data should never go external, via
# email, for instance
return $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);
}
function markUnAnswered() {
return (!$this->isAnswered() || $this->setAnsweredState(0));
}
function markAnswered() {
return ($this->isAnswered() || $this->setAnsweredState(1));
}
function markOverdue($whine=true) {
Peter Rotich
committed
Peter Rotich
committed
if($this->isOverdue())
return true;
$sql='UPDATE '.TICKET_TABLE.' SET isoverdue=1, updated=NOW() '
.' WHERE ticket_id='.db_input($this->getId());
if(!db_query($sql) || !db_affected_rows())
return false;
$this->logEvent('overdue');
Peter Rotich
committed
if(!$this->isOverdue())
Peter Rotich
committed
//NOTE: Previously logged overdue event is NOT annuled.
$sql='UPDATE '.TICKET_TABLE.' SET isoverdue=0, updated=NOW() ';
Peter Rotich
committed
if($this->getDueDate() && Misc::db2gmtime($this->getDueDate()) <= Misc::gmtime())
Peter Rotich
committed
//Clear SLA if est. due date is in the past
if($this->getSLADueDate() && Misc::db2gmtime($this->getSLADueDate()) <= Misc::gmtime())
Peter Rotich
committed
$sql.=', sla_id=0 ';
$sql.=' WHERE ticket_id='.db_input($this->getId());
return (db_query($sql) && db_affected_rows());
}
Peter Rotich
committed
//Dept Tranfer...with alert.. done by staff
function transfer($deptId, $comments, $alert = true) {
Peter Rotich
committed
Peter Rotich
committed
if(!$thisstaff || !$thisstaff->canTransferTickets())
$currentDept = $this->getDeptName(); //Current department
if(!$deptId || !$this->setDeptId($deptId))
return false;
Peter Rotich
committed
// Reopen ticket if closed
if($this->isClosed()) $this->reopen();
$this->reload();
// Set SLA of the new department
if(!$this->getSLAId() || $this->getSLA()->isTransient())
$this->selectSLAId();
Peter Rotich
committed
/*** log the transfer comments as internal note - with alerts disabled - ***/
$title='Ticket transfered from '.$currentDept.' to '.$this->getDeptName();
Peter Rotich
committed
$comments=$comments?$comments:$title;
$note = $this->logNote($title, $comments, $thisstaff, false);
Peter Rotich
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();
Peter Rotich
committed
//Email to use!
if(!($email=$cfg->getAlertEmail()))
$email =$cfg->getDefaultEmail();
Peter Rotich
committed
//Get the message template
if($tpl && ($msg=$tpl->getTransferAlertMsgTemplate()) && $email) {
Peter Rotich
committed
$msg = $this->replaceVars($msg->asArray(),
array('comments' => $comments, 'staff' => $thisstaff));
Peter Rotich
committed
//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.
$recipients+=$members;
}
//Always alert dept manager??
if($cfg->alertDeptManagerONTransfer() && $dept && ($manager=$dept->getManager()))
$recipients[]= $manager;
Peter Rotich
committed
$options = array('references' => $note->getEmailMessageId());
Peter Rotich
committed
foreach( $recipients as $k=>$staff) {
if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
$alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
$email->sendAlert($staff->getEmail(), $msg['subj'], $alert,
null, $options);
}
}
return true;
}
function assignToStaff($staff, $note, $alert=true) {
if(!is_object($staff) && !($staff=Staff::lookup($staff)))
return false;
Peter Rotich
committed
if(!$this->setStaffId($staff->getId()))
return false;
$this->logEvent('assigned');
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->logEvent('assigned');
return true;
}
//Assign ticket to staff or team - overloaded ID.
function assign($assignId, $note, $alert=true) {
global $thisstaff;
$rv=0;
Peter Rotich
committed
$id=preg_replace("/[^0-9]/", "", $assignId);
if($assignId[0]=='t') {
$rv=$this->assignToTeam($id, $note, $alert);
} elseif($assignId[0]=='s' || is_numeric($assignId)) {
$alert=($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;
}
Peter Rotich
committed
//unassign primary assignee
function unassign() {
if(!$this->isAssigned()) //We can't release what is not assigned buddy!
return true;
//We can only unassigned OPEN tickets.
if($this->isClosed())
return false;
//Unassign staff (if any)
if($this->getStaffId() && !$this->setStaffId(0))
return false;
//unassign team (if any)
if($this->getTeamId() && !$this->setTeamId(0))
return false;
$this->reload();
return true;
}
Peter Rotich
committed
function release() {
return $this->unassign();
}
//Insert message from client
function postMessage($vars, $origin='', $alerts=true) {
Peter Rotich
committed
//Strip quoted reply...on emailed replies
if(!strcasecmp($origin, 'Email')
&& $cfg->stripQuotedReply()
&& ($tag=$cfg->getReplySeparator()) && strpos($vars['message'], $tag))
if(list($msg) = split($tag, $vars['message']))
$vars['message'] = $msg;
Peter Rotich
committed
$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());
if(!$alerts) return $message; //Our work is done...
if ($autorespond && $message->isAutoResponse())
$this->onMessage($autorespond, $message); //must be called b4 sending alerts to staff.
$dept = $this->getDept();
if(!$dept || !($tpl = $dept->getTemplate()))
$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())) {
$attachments = $message->getAttachments();
$msg = $this->replaceVars($msg->asArray(), array('message' => $message));
//Build list of recipients and fire the alerts.
$recipients=array();
//Last respondent.
if($cfg->alertLastRespondentONNewMessage() || $cfg->alertAssignedONNewMessage())
$recipients[]=$this->getLastRespondent();
Peter Rotich
committed
//Assigned staff if any...could be the last respondent
Peter Rotich
committed
if($this->isAssigned() && ($staff=$this->getStaff()))
$recipients[]=$staff;
Peter Rotich
committed
//Dept manager
if($cfg->alertDeptManagerONNewMessage() && $dept && ($manager=$dept->getManager()))
$recipients[]=$manager;
Peter Rotich
committed
$sentlist=array(); //I know it sucks...but..it works.
$options = array('references'=>$message->getEmailMessageId());
Peter Rotich
committed
foreach( $recipients as $k=>$staff) {
if(!$staff || !$staff->getEmail() || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
$alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
$email->sendAlert($staff->getEmail(), $msg['subj'], $alert,
$attachments, $options);
return $message;
function postCannedReply($canned, $msgId, $alert=true) {
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);
if(!($response=$this->postReply($info, $errors, false)))
return null;
if(!$alert) return $response;
$dept = $this->getDept();
if(!($tpl = $dept->getTemplate()))
$tpl= $cfg->getDefaultTemplate();
if(!$dept || !($email=$dept->getEmail()))
$email = $cfg->getDefaultEmail();
if($tpl && ($msg=$tpl->getAutoReplyMsgTemplate()) && $email) {
if($dept && $dept->isPublic())
$signature=$dept->getSignature();
else
$signature='';
$attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array();
$msg = $this->replaceVars($msg->asArray(),
array('response' => $response, 'signature' => $signature));
if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
$msg['body'] ="\n$tag\n\n".$msg['body'];
$options = array('references' => $response->getEmailMessageId());
$email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], $attachments,
$options);
return $response;
function postReply($vars, &$errors, $alert = true) {
if(!$vars['poster'] && $thisstaff)
$vars['poster'] = $thisstaff->getName();
if(!$vars['staffId'] && $thisstaff)
$vars['staffId'] = $thisstaff->getId();
if(!($response = $this->getThread()->addResponse($vars, $errors)))
return null;
//Set status - if checked.
if(isset($vars['reply_ticket_status']) && $vars['reply_ticket_status'])
$this->setStatus($vars['reply_ticket_status']);
$this->onResponse(); //do house cleaning..
$this->reload();
/* email the user?? - if disabled - the bail out */
if(!$alert) return $response;
if(!($tpl = $dept->getTemplate()))
$tpl= $cfg->getDefaultTemplate();
if(!$dept || !($email=$dept->getEmail()))
$email = $cfg->getDefaultEmail();
if($tpl && ($msg=$tpl->getReplyMsgTemplate()) && $email) {
if($thisstaff && $vars['signature']=='mine')
$signature=$thisstaff->getSignature();
elseif($vars['signature']=='dept' && $dept && $dept->isPublic())
$signature=$dept->getSignature();
else
$signature='';
//Set attachments if emailing.
$attachments = $cfg->emailAttachments()?$response->getAttachments():array();
$msg = $this->replaceVars($msg->asArray(),
array('response' => $response, 'signature' => $signature, 'staff' => $thisstaff));
if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
$options = array('references' => $response->getEmailMessageId());
//TODO: setup 5 param (options... e.g mid trackable on replies)
$email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments,
$options);
return $response;
}
//Activity log - saved as internal notes WHEN enabled!!
Peter Rotich
committed
function logActivity($title, $note) {
global $cfg;
if(!$cfg || !$cfg->logTicketActivity())
return 0;
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;
}
function logNote($title, $note, $poster='SYSTEM', $alert=true) {
return $this->postNote(
array('title' => $title, 'note' => $note),
$errors,
$poster,
$alert);
}
function postNote($vars, &$errors, $poster, $alert=true) {
//Who is posting the note - staff or system?
$vars['staffId'] = 0;
$vars['poster'] = 'SYSTEM';
$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!
if(isset($vars['state']) && $vars['state']) {
if($this->setState($vars['state']))
$this->reload();
}
// If alerts are not enabled then return a success.
if(!$alert || !$cfg->alertONNewNote() || !($dept=$this->getDept()))
return $note;
if(!($tpl = $dept->getTemplate()))
$tpl= $cfg->getDefaultTemplate();
if(!($email=$cfg->getAlertEmail()))
$email =$cfg->getDefaultEmail();
if($tpl && ($msg=$tpl->getNoteAlertMsgTemplate()) && $email) {
Peter Rotich
committed
$attachments = $note->getAttachments();
$msg = $this->replaceVars($msg->asArray(),
array('note' => $note));
Peter Rotich
committed
// Alert recipients
Peter Rotich
committed
//Last respondent.
if($cfg->alertLastRespondentONNewNote())
$recipients[]=$this->getLastRespondent();
Peter Rotich
committed
//Assigned staff if any...could be the last respondent
if($cfg->alertAssignedONNewNote() && $this->isAssigned() && $this->getStaffId())
$recipients[]=$this->getStaff();
Peter Rotich
committed
//Dept manager
if($cfg->alertDeptManagerONNewNote() && $dept && $dept->getManagerId())
$recipients[]=$dept->getManager();
$options = array('references' => $note->getEmailMessageId());
$sentlist=array();
foreach( $recipients as $k=>$staff) {
Peter Rotich
committed
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;
Peter Rotich
committed
$alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
$email->sendAlert($staff->getEmail(), $msg['subj'], $alert, $attachments,
$options);
return $note;
//Print ticket... export the ticket thread as PDF.
function pdfExport($psize='Letter', $notes=false) {
$pdf = new Ticket2PDF($this, $psize, $notes);
$name='Ticket-'.$this->getExtId().'.pdf';
//Remember what the user selected - for autoselect on the next print.
$_SESSION['PAPER_SIZE'] = $psize;
function delete() {
$sql = 'DELETE FROM '.TICKET_TABLE.' WHERE ticket_id='.$this->getId().' LIMIT 1';
if(!db_query($sql) || !db_affected_rows())
return false;
//delete just orphaned ticket thread & associated attachments.
$this->getThread()->delete();
return true;
}
function update($vars, &$errors) {
global $cfg, $thisstaff;
if(!$cfg || !$thisstaff || !$thisstaff->canEditTickets())
return false;
$fields=array();
$fields['topicId'] = array('type'=>'int', 'required'=>1, 'error'=>'Help topic required');
$fields['slaId'] = array('type'=>'int', 'required'=>0, 'error'=>'Select SLA');
$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';
Peter Rotich
committed
if($vars['duedate']) {
$errors['duedate']='Due date 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 due date';
elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time())
$errors['duedate']='Due date must be in the future';
}
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'])
.' ,duedate='.($vars['duedate']?db_input(date('Y-m-d G:i',Misc::dbtime($vars['duedate'].' '.$vars['time']))):'NULL');
Peter Rotich
committed
if($vars['duedate']) { //We are setting new duedate...
$sql.=' ,isoverdue=0';
}
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());
$this->logNote('Ticket Updated', $vars['note'], $thisstaff);
Peter Rotich
committed
// Reselect SLA if transient
if(!$this->getSLAId() || $this->getSLA()->isTransient())
$this->selectSLAId();
Peter Rotich
committed
//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
committed
/*============== Static functions. Use Ticket::function(params); =============nolint*/
function getIdByExtId($extId, $email=null) {
Peter Rotich
committed
if(!$extId || !is_numeric($extId))
$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);
Peter Rotich
committed
$sql .= ' AND email.address = '.db_input($email);
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));
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;
}
Peter Rotich
committed
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);
$id=0;
if(($res=db_query($sql)) && db_num_rows($res))
list($id)=db_fetch_row($res);
return $id;
}
Peter Rotich
committed
/* Quick staff's tickets stats */
function getStaffStats($staff) {
global $cfg;
Peter Rotich
committed
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
Peter Rotich
committed
ON (open.ticket_id=ticket.ticket_id
AND open.status=\'open\'
Peter Rotich
committed
AND open.isanswered=0
'.((!($cfg->showAssignedTickets() || $staff->showAssignedTickets()))?
' AND open.staff_id=0 ':'').') '
Peter Rotich
committed
ON (answered.ticket_id=ticket.ticket_id
AND answered.status=\'open\'
Peter Rotich
committed
AND answered.isanswered=1) '
Peter Rotich
committed
ON (overdue.ticket_id=ticket.ticket_id
AND overdue.status=\'open\'
Peter Rotich
committed
AND overdue.isoverdue=1) '
Peter Rotich
committed
ON (assigned.ticket_id=ticket.ticket_id
AND assigned.status=\'open\'
Peter Rotich
committed
AND assigned.staff_id='.db_input($staff->getId()).')'
Peter Rotich
committed
ON (closed.ticket_id=ticket.ticket_id
.' WHERE (ticket.staff_id='.db_input($staff->getId());
$sql.=' OR ticket.team_id IN('.implode(',', db_input(array_filter($teams))).')';
if(!$staff->showAssignedOnly() && ($depts=$staff->getDepts())) //Staff with limited access just see Assigned tickets.
$sql.=' OR ticket.dept_id IN('.implode(',', db_input($depts)).') ';
$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));
}
Peter Rotich
committed
/* Quick client's tickets stats
@email - valid email.
*/
function getClientStats($email) {
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));
}
/*
* The mother of all functions...You break it you fix it!
*
* $autorespond and $alertstaff overrides config settings...
Peter Rotich
committed
*/
function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) {
global $ost, $cfg, $thisclient, $_FILES;
foreach (array('phone', 'subject') as $f)
if (isset($vars[$f]))
$vars[$f] = trim($vars[$f]);
//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';
$ost->logWarning('Ticket denied', 'Banned email - '.$vars['email']);
return 0;
}
//Make sure the open ticket limit hasn't been reached. (LOOP CONTROL)
Peter Rotich
committed
if($cfg->getMaxOpenTickets()>0 && strcasecmp($origin,'staff')
&& ($client=Client::lookupByEmail($vars['email']))
&& ($openTickets=$client->getNumOpenTickets())
&& ($openTickets>=$cfg->getMaxOpenTickets()) ) {
$errors['err']="You've reached the maximum open tickets allowed.";
Peter Rotich
committed
$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::getInstance();
// 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 = $vars[$fname];
}
// Don't enforce form validation for email
if (!$form->isValid() && strtolower($origin) != 'email')
$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());
//Init ticket filters...
$ticket_filter = new TicketFilter($origin, $vars);
// Make sure email contents should not be rejected
Peter Rotich
committed
if($ticket_filter
&& ($filter=$ticket_filter->shouldReject())) {
$errors['err']='Ticket denied. Error #403';
Peter Rotich
committed
$ost->logWarning('Ticket denied',
sprintf('Ticket rejected ( %s) by filter "%s"',
$vars['email'], $filter->getName()));
$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');
$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';
Peter Rotich
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']) {
if(!$vars['time'] || strpos($vars['time'],':')===false)
$errors['time']='Select time';
elseif(strtotime($vars['duedate'].' '.$vars['time'])===false)
$errors['duedate']='Invalid due date';
elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time())
$errors['duedate']='Due date must be in the future';
}
# Identify the user creating the ticket
$user_info = UserForm::getStaticForm()->getClean();
// Data is slightly different between HTTP posts and emails
if (isset($vars['emailId']) || !isset($user_info['email']))
$user_info = $vars;
elseif (!UserForm::getStaticForm()->isValid())
$errors['user'] = 'Incomplete client information';
$user = User::fromForm($user_info);
$user_email = UserEmail::ensure($user_info['email']);
//Any error above is fatal.
if($errors) return 0;
# Perform ticket filter actions on the new ticket arguments
if ($ticket_filter) $ticket_filter->apply($vars);
# 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
if(isset($vars['topicId']) && ($topic=Topic::lookup($vars['topicId']))) { //Ticket created via web by user/or staff
$deptId=$deptId?$deptId:$topic->getDeptId();
if (!$form->getAnswer('priority'))
$form->setAnswer('priority', null, $topic->getPriorityId());
if($autorespond) $autorespond=$topic->autoRespond();
$source=$vars['source']?$vars['source']:'Web';
//Auto assignment.
if (!isset($vars['staffId']) && $topic->getStaffId())
$vars['staffId'] = $topic->getStaffId();
elseif (!isset($vars['teamId']) && $topic->getTeamId())
$vars['teamId'] = $topic->getTeamId();
//set default sla.
if(isset($vars['slaId']))
$vars['slaId'] = $vars['slaId']?$vars['slaId']:$cfg->getDefaultSLAId();
elseif($topic && $topic->getSLAId())
$vars['slaId'] = $topic->getSLAId();
}elseif($vars['emailId'] && !$vars['deptId'] && ($email=Email::lookup($vars['emailId']))) { //Emailed Tickets
$deptId=$email->getDeptId();
if (!$form->getAnswer('priority'))
$form->setAnswer('priority', null, $email->getPriorityId());
if($autorespond) $autorespond=$email->autoRespond();