From 304dff4b2d7981d4d8899bc359ecd41f6b30b663 Mon Sep 17 00:00:00 2001 From: Peter Rotich Date: Mon, 4 Mar 2013 23:05:19 -0500 Subject: [PATCH] Refactor ticket related methods/utils to use thread model. --- include/class.mailfetch.php | 27 +- include/class.pdf.php | 31 +- include/class.ticket.php | 472 ++++++++---------------------- include/client/view.inc.php | 4 +- include/staff/ticket-view.inc.php | 15 +- scp/tickets.php | 80 +++-- 6 files changed, 218 insertions(+), 411 deletions(-) diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index 5253dfe8..c7612ccb 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -400,11 +400,11 @@ function createTicket($mid) { $errors=array(); if($ticket) { - if(!($msgid=$ticket->postMessage($vars, 'Email'))) + if(!($message=$ticket->postMessage($vars, 'Email'))) return false; } elseif (($ticket=Ticket::create($vars, $errors, 'Email'))) { - $msgid = $ticket->getLastMsgId(); + $message = $ticket->getLastMessage(); } else { //Report success if the email was absolutely rejected. if(isset($errors['errno']) && $errors['errno'] == 403) @@ -421,19 +421,22 @@ function createTicket($mid) { } //Save attachments if any. - if($msgid + if($message && $ost->getConfig()->allowEmailAttachments() - && ($struct = imap_fetchstructure($this->mbox, $mid)) - && $struct->parts + && ($struct = imap_fetchstructure($this->mbox, $mid)) + && $struct->parts && ($attachments=$this->getAttachments($struct))) { - + foreach($attachments as $a ) { - $file = array( - 'name' => $a['name'], - 'type' => $a['type'], - 'data' => $this->decode(imap_fetchbody($this->mbox, $mid, $a['index']), $a['encoding']) - ); - $ticket->importAttachments(array($file), $msgid, 'M'); + $file = array('name' => $a['name'], 'type' => $a['type']); + + //Check the file type + if(!$ost->isFileTypeAllowed($file)) + $file['error'] = 'Invalid file type (ext) for '.Format::htmlchars($file['name']); + else //only fetch the body if necessary TODO: Make it a callback. + $file['data'] = $this->decode(imap_fetchbody($this->mbox, $mid, $a['index']), $a['encoding']); + + $message->importAttachment($file); } } diff --git a/include/class.pdf.php b/include/class.pdf.php index b0650b77..f2101493 100644 --- a/include/class.pdf.php +++ b/include/class.pdf.php @@ -21,11 +21,11 @@ class.pdf.php class Ticket2PDF extends FPDF { - + var $includenotes = false; - + var $pageOffset = 0; - + var $ticket = null; function Ticket2PDF($ticket, $psize='Letter', $notes=false) { @@ -47,7 +47,7 @@ function Ticket2PDF($ticket, $psize='Letter', $notes=false) { function getTicket() { return $this->ticket; } - + //report header...most stuff are hard coded for now... function Header() { global $cfg; @@ -66,7 +66,7 @@ function Header() { $this->Cell(0, 5, 'Date & Time based on GMT '.$_SESSION['TZ_OFFSET'], 0, 1, 'R'); $this->Ln(10); } - + //Page footer baby function Footer() { global $thisstaff; @@ -94,10 +94,10 @@ function _utf8($text) { if(function_exists('iconv')) return iconv('UTF-8', 'windows-1252', $text); - + return utf8_encode($text); } - + function _print() { if(!($ticket=$this->getTicket())) @@ -107,7 +107,7 @@ function _print() { $l = 35; $c = $w-$l; - + $this->SetFont('Arial', 'B', 11); $this->cMargin = 0; $this->SetFont('Arial', 'B', 11); @@ -215,7 +215,11 @@ function _print() { 'R'=>array(255, 224, 179), 'N'=>array(250, 250, 210)); //Get ticket thread - if(($entries = $ticket->getThread(($this->includenotes)))) { + $types = array('M', 'R'); + if($this->includenotes) + $types[] = 'N'; + + if(($entries = $ticket->getThreadEntries($types))) { foreach($entries as $entry) { $color = $colors[$entry['thread_type']]; @@ -228,11 +232,12 @@ function _print() { $this->Cell($w/2, 7, $entry['poster'], 'TBR', 1, 'L', true); $this->SetFont(''); $text= $entry['body']; - if($entry['attachments'] - && ($attachments = $ticket->getAttachments($entry['id'], $entry['thread_type']))) { + if($entry['attachments'] + && ($tentry=$ticket->getThreadEntry($entry['id'])) + && ($attachments = $tentry->getAttachments())) { foreach($attachments as $attachment) $files[]= $attachment['name']; - + $text.="\nFiles Attached: [".implode(', ',$files)."]\n"; } $this->WriteText($w*2, $text, 1); @@ -240,6 +245,6 @@ function _print() { } } - } + } } ?> diff --git a/include/class.ticket.php b/include/class.ticket.php index a7a16e46..4e9cff30 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -64,7 +64,9 @@ class Ticket { var $team; //Team obj var $topic; //Topic obj var $tlock; //TicketLock obj - + + var $thread; //Thread obj. + function Ticket($id){ $this->id = 0; $this->load($id); @@ -78,11 +80,8 @@ function load($id=0) { //TODO: delete helptopic field in ticket table. $sql='SELECT ticket.*, lock_id, dept_name, priority_desc ' - .' ,IF(sla.id IS NULL, NULL, DATE_ADD(ticket.created, INTERVAL sla.grace_period HOUR)) as sla_duedate ' + .' ,IF(sla.id IS NULL, NULL, DATE_ADD(ticket.created, INTERVAL sla.grace_period HOUR)) as sla_duedate ' .' ,count(attach.attach_id) as attachments ' - .' ,count(DISTINCT message.id) as messages ' - .' ,count(DISTINCT response.id) as responses ' - .' ,count(DISTINCT note.id) as notes ' .' FROM '.TICKET_TABLE.' ticket ' .' LEFT JOIN '.DEPT_TABLE.' dept ON (ticket.dept_id=dept.dept_id) ' .' LEFT JOIN '.SLA_TABLE.' sla ON (ticket.sla_id=sla.id AND sla.isactive=1) ' @@ -92,12 +91,6 @@ function load($id=0) { .'ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW()) ' .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (' .'ticket.ticket_id=attach.ticket_id) ' - .' LEFT JOIN '.TICKET_THREAD_TABLE.' message ON (' - ."ticket.ticket_id=message.ticket_id AND message.thread_type = 'M') " - .' LEFT JOIN '.TICKET_THREAD_TABLE.' response ON (' - ."ticket.ticket_id=response.ticket_id AND response.thread_type = 'R') " - .' LEFT JOIN '.TICKET_THREAD_TABLE.' note ON ( ' - ."ticket.ticket_id=note.ticket_id AND note.thread_type = 'N') " .' WHERE ticket.ticket_id='.db_input($id) .' GROUP BY ticket.ticket_id'; @@ -143,7 +136,10 @@ function load($id=0) { $this->tlock = null; $this->stats = null; $this->topic = null; - + $this->thread = null; + + $this->getThread(); + return true; } @@ -261,13 +257,13 @@ function getDueDate(){ return $this->duedate; } - function getSLADuedate() { + function getSLADueDate() { return $this->ht['sla_duedate']; } function getEstDueDate() { - //Real due date + //Real due date if(($duedate=$this->getDueDate())) return $duedate; @@ -534,12 +530,16 @@ function getLastMsgId() { return $this->lastMsgId; } - function getRelatedTicketsCount(){ + function getLastMessage() { + return Message::lookup($this->getLastMsgId(), $this->getId()); + } + + function getThread() { - $sql='SELECT count(*) FROM '.TICKET_TABLE - .' WHERE email='.db_input($this->getEmail()); + if(!$this->thread) + $this->thread = Thread::lookup($this); - return db_result(db_query($sql)); + return $this->thread; } function getThreadCount() { @@ -547,121 +547,42 @@ function getThreadCount() { } function getNumMessages() { - return $this->ht['messages']; + return $this->getThread()->getNumMessages(); } function getNumResponses() { - return $this->ht['responses']; + return $this->getThread()->getNumResponses(); } function getNumNotes() { - return $this->ht['notes']; + return $this->getThread()->getNumNotes(); } function getMessages() { - return $this->getThreadByType('M'); + return $this->getThreadEntries('M'); } - function getResponses($msgId=0) { - return $this->getThreadByType('R', $msgId); + function getResponses() { + return $this->getThreadEntries('R'); } function getNotes() { - return $this->getThreadByType('N'); + return $this->getThreadEntries('N'); } function getClientThread() { - return $this->getThreadWithoutNotes(); + return $this->getThreadEntries(array('M', 'R')); } - function getThreadWithNotes() { - return $this->getThread(true); + function getThreadEntry($id) { + return $this->getThread()->getEntry($id); } - - function getThreadWithoutNotes() { - return $this->getThread(false); - } - - function getThread($includeNotes=false, $order='') { - $treadtypes=array('M', 'R'); // messages and responses. - if($includeNotes) //Include notes?? - $treadtypes[] = 'N'; - - return $this->getThreadByType($treadtypes, $order); + function getThreadEntries($type, $order='') { + return $this->getThread()->getEntries($type, $order); } - - function getThreadByType($type, $order='ASC') { - - if(!$order || !in_array($order, array('DESC','ASC'))) - $order='ASC'; - - $sql='SELECT thread.* ' - .' ,count(DISTINCT attach.attach_id) as attachments ' - .' FROM '.TICKET_THREAD_TABLE.' thread ' - .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach - ON (thread.ticket_id=attach.ticket_id - AND thread.id=attach.ref_id - AND thread.thread_type=attach.ref_type) ' - .' WHERE thread.ticket_id='.db_input($this->getId()); - - if($type && is_array($type)) - $sql.=" AND thread.thread_type IN('".implode("','", $type)."')"; - elseif($type) - $sql.=' AND thread.thread_type='.db_input($type); - - $sql.=' GROUP BY thread.id ' - .' ORDER BY thread.created '.$order; - $thread=array(); - if(($res=db_query($sql)) && db_num_rows($res)) - while($rec=db_fetch_array($res)) - $thread[] = $rec; - - return $thread; - } - - function getAttachments($refId=0, $type=null) { - - if($refId && !$type) - return NULL; - - //XXX: inner join the file table instead? - $sql='SELECT a.attach_id, f.id as file_id, f.size, f.hash as file_hash, f.name ' - .' FROM '.FILE_TABLE.' f ' - .' INNER JOIN '.TICKET_ATTACHMENT_TABLE.' a ON(f.id=a.file_id) ' - .' WHERE a.ticket_id='.db_input($this->getId()); - - if($refId) - $sql.=' AND a.ref_id='.db_input($refId); - - if($type) - $sql.=' AND a.ref_type='.db_input($type); - - $attachments = array(); - if(($res=db_query($sql)) && db_num_rows($res)) { - while($rec=db_fetch_array($res)) - $attachments[] = $rec; - } - - return $attachments; - } - - function getAttachmentsLinks($refId, $type, $separator=' ',$target='') { - - $str=''; - foreach($this->getAttachments($refId, $type) as $attachment ) { - /* The has here can be changed but must match validation in attachment.php */ - $hash=md5($attachment['file_id'].session_id().$attachment['file_hash']); - if($attachment['size']) - $size=sprintf('(%s)', Format::file_size($attachment['size'])); - - $str.=sprintf('%s%s %s', - $attachment['attach_id'], $hash, $target, Format::htmlchars($attachment['name']), $size, $separator); - } - return $str; - } /* -------------------- Setters --------------------- */ function setLastMsgId($msgid) { @@ -1168,10 +1089,10 @@ function getVar($tag) { break; case 'due_date': $duedate =''; - if($this->getDueDate()) + if($this->getEstDueDate()) $duedate = Format::date( $cfg->getDateTimeFormat(), - Misc::db2gmtime($this->getDueDate()), + Misc::db2gmtime($this->getEstDueDate()), $cfg->getTZOffset(), $cfg->observeDaylightSaving()); @@ -1397,46 +1318,34 @@ function release() { } //Insert message from client - function postMessage($vars, $source='', $alerts=true) { + function postMessage($vars, $origin='', $alerts=true) { global $cfg; - - if(!$vars || !$vars['message']) - return 0; - + //Strip quoted reply...on emailed replies - if(!strcasecmp($source, 'Email') - && $cfg->stripQuotedReply() + if(!strcasecmp($origin, 'Email') + && $cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()) && strpos($vars['message'], $tag)) - list($vars['message']) = split($tag, $vars['message']); + if(list($msg) = split($tag, $vars['message'])) + $vars['message'] = $msg; - # XXX: Refuse auto-response messages? (via email) XXX: No - but kill our auto-responder. + if($vars['ip']) + $vars['ip_address'] = $vars['ip']; + elseif(!$vars['ip_address'] && $_SERVER['REMOTE_ADDR']) + $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; + $errors = array(); + if(!($message = $this->getThread()->addMessage($vars, $errors))) + return null; - $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW()' - .' ,thread_type="M" ' - .' ,ticket_id='.db_input($this->getId()) - # XXX: Put Subject header into the 'title' field - .' ,body='.db_input(Format::striptags($vars['message'])) //Tags/code stripped...meaning client can not send in code..etc - .' ,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($message->getId()); - $this->setLastMsgId($msgid); - - if (isset($vars['mid'])) { - $sql='INSERT INTO '.TICKET_EMAIL_INFO_TABLE - .' SET message_id='.db_input($msgid) - .', email_mid='.db_input($vars['mid']) - .', headers='.db_input($vars['header']); - db_query($sql); - } + if (isset($vars['mid'])) + $message->saveEmailInfo($vars); - if(!$alerts) return $msgid; //Our work is done... + if(!$alerts) return $message; //Our work is done... $autorespond = true; - if ($autorespond && $vars['header'] && TicketFilter::isAutoResponse($vars['header'])) + if ($autorespond && $message->isAutoResponse()) $autorespond=false; $this->onMessage($autorespond); //must be called b4 sending alerts to staff. @@ -1452,7 +1361,7 @@ function postMessage($vars, $source='', $alerts=true) { //If enabled...send alert to staff (New Message Alert) if($cfg->alertONNewMessage() && $tpl && $email && ($msg=$tpl->getNewMessageAlertMsgTemplate())) { - $msg = $this->replaceVars($msg, array('message' => $vars['message'])); + $msg = $this->replaceVars($msg, array('message' => $message)); //Build list of recipients and fire the alerts. $recipients=array(); @@ -1477,8 +1386,8 @@ function postMessage($vars, $source='', $alerts=true) { $sentlist[] = $staff->getEmail(); } } - - return $msgid; + + return $message; } function postCannedReply($canned, $msgId, $alert=true) { @@ -1492,16 +1401,17 @@ function postCannedReply($canned, $msgId, $alert=true) { $files[] = $file['id']; $info = array('msgId' => $msgId, + 'poster' => 'SYSTEM (Canned Reply)', 'response' => $this->replaceVars($canned->getResponse()), 'cannedattachments' => $files); $errors = array(); - if(!($respId=$this->postReply($info, $errors, false))) - return false; + if(!($response=$this->postReply($info, $errors, false))) + return null; $this->markUnAnswered(); - if(!$alert) return $respId; + if(!$alert) return $response; $dept = $this->getDept(); @@ -1518,65 +1428,41 @@ function postCannedReply($canned, $msgId, $alert=true) { else $signature=''; - $msg = $this->replaceVars($msg, array('response' => $info['response'], 'signature' => $signature)); + $msg = $this->replaceVars($msg, array('response' => $response, 'signature' => $signature)); if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) $msg['body'] ="\n$tag\n\n".$msg['body']; - $attachments =($cfg->emailAttachments() && $files)?$this->getAttachments($respId, 'R'):array(); + $attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array(); $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], $attachments); } - return $respId; + return $response; } - /* public */ + /* public */ function postReply($vars, &$errors, $alert = true) { global $thisstaff, $cfg; - if(!$vars['msgId']) - $errors['msgId'] ='Missing messageId - internal error'; - if(!$vars['response']) - $errors['response'] = 'Response message required'; - - if($errors) return 0; - $poster = $thisstaff?$thisstaff->getName():'SYSTEM (Canned Reply)'; + if(!$vars['poster'] && $thisstaff) + $vars['poster'] = $thisstaff->getName(); - $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() ' - .' ,thread_type="R"' - .' ,ticket_id='.db_input($this->getId()) - .' ,pid='.db_input($vars['msgId']) - .' ,body='.db_input(Format::striptags($vars['response'])) - .' ,staff_id='.db_input($thisstaff?$thisstaff->getId():0) - .' ,poster='.db_input($poster) - .' ,ip_address='.db_input($thisstaff?$thisstaff->getIP():''); + if(!$vars['staffId'] && $thisstaff) + $vars['staffId'] = $thisstaff->getId(); - if(!db_query($sql) || !($respId=db_insert_id())) - return false; + 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']); - /* We can NOT recover from attachment related failures at this point */ - $attachments = array(); - //Web based upload.. note that we're not "validating" attachments on response. - if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments']))) - $attachments=$this->uploadAttachments($files, $respId, 'R'); - - //Canned attachments... - if($vars['cannedattachments'] && is_array($vars['cannedattachments'])) { - foreach($vars['cannedattachments'] as $fileId) - if($fileId && $this->saveAttachment($fileId, $respId, 'R')) - $attachments[] = $fileId; - } - $this->onResponse(); //do house cleaning.. $this->reload(); /* email the user?? - if disabled - the bail out */ - if(!$alert) return $respId; + if(!$alert) return $response; $dept = $this->getDept(); @@ -1594,20 +1480,20 @@ function postReply($vars, &$errors, $alert = true) { $signature=$dept->getSignature(); else $signature=''; - - $msg = $this->replaceVars($msg, - array('response' => $vars['response'], 'signature' => $signature, 'staff' => $thisstaff)); + + $msg = $this->replaceVars($msg, + array('response' => $response, 'signature' => $signature, 'staff' => $thisstaff)); if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) $msg['body'] ="\n$tag\n\n".$msg['body']; //Set attachments if emailing. - $attachments =($cfg->emailAttachments() && $attachments)?$this->getAttachments($respId,'R'):array(); + $attachments = $cfg->emailAttachments()?$response->getAttachments():array(); //TODO: setup 5 param (options... e.g mid trackable on replies) $email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments); } - return $respId; + return $response; } //Activity log - saved as internal notes WHEN enabled!! @@ -1659,42 +1545,23 @@ function logNote($title, $note, $poster='SYSTEM', $alert=true) { $alert); } - function postNote($vars, &$errors, $poster, $alert=true) { + function postNote($vars, &$errors, $poster, $alert=true) { global $cfg, $thisstaff; - if(!$vars || !is_array($vars)) - $errors['err'] = 'Missing or invalid data'; - elseif(!$vars['note']) - $errors['note'] = 'Note required'; - - if($errors) return false; - - $staffId = 0; + //Who is posting the note - staff or system? + $vars['staffId'] = 0; + $vars['poster'] = 'SYSTEM'; if($poster && is_object($poster)) { - $staffId = $poster->getId(); - $poster = $poster->getName(); - } elseif(!$poster) { - $poster ='SYSTEM'; + $vars['staffId'] = $poster->getId(); + $vars['poster'] = $poster->getName(); + }elseif($poster) { //string + $vars['poster'] = $poster; } - //TODO: move to class.thread.php + if(!($note=$this->getThread()->addNote($vars, $errors))) + return null; - $sql= 'INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() '. - ',thread_type="N"'. - ',ticket_id='.db_input($this->getId()). - ',title='.db_input(Format::striptags($vars['title']?$vars['title']:'[No Title]')). - ',body='.db_input(Format::striptags($vars['note'])). - ',staff_id='.db_input($staffId). - ',poster='.db_input($poster); - //echo $sql; - if(!db_query($sql) || !($id=db_insert_id())) - return false; - - //Upload attachments IF ANY - TODO: validate attachment types?? - if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments']))) - $attachments = $this->uploadAttachments($files, $id, 'N'); - - //Set state: Error on state change not critical! + //Set state: Error on state change not critical! if(isset($vars['state']) && $vars['state']) { if($this->setState($vars['state'])) $this->reload(); @@ -1702,11 +1569,8 @@ function postNote($vars, &$errors, $poster, $alert=true) { // If alerts are not enabled then return a success. if(!$alert || !$cfg->alertONNewNote() || !($dept=$this->getDept())) - return $id; + return $note; - //Note obj. - $note = Note::lookup($id, $this->getId()); - if(!($tpl = $dept->getTemplate())) $tpl= $cfg->getDefaultTemplate(); @@ -1733,7 +1597,7 @@ function postNote($vars, &$errors, $poster, $alert=true) { if($cfg->alertDeptManagerONNewNote() && $dept && $dept->getManagerId()) $recipients[]=$dept->getManager(); - $attachments =($attachments)?$this->getAttachments($id, 'N'):array(); + $attachments = $note->getAttachments(); $sentlist=array(); foreach( $recipients as $k=>$staff) { if(!$staff || !is_object($staff) || !$staff->getEmail() || !$staff->isAvailable()) continue; @@ -1743,8 +1607,8 @@ function postNote($vars, &$errors, $poster, $alert=true) { $sentlist[] = $staff->getEmail(); } } - - return $id; + + return $note; } //Print ticket... export the ticket thread as PDF. @@ -1757,123 +1621,25 @@ function pdfExport($psize='Letter', $notes=false) { exit; } - //online based attached files. - function uploadAttachments($files, $refid, $type, $checkFileTypes=false) { - global $ost; - - $uploaded=array(); - $ost->validateFileUploads($files, $checkFileTypes); //Validator sets errors - if any - foreach($files as $file) { - if(!$file['error'] - && ($id=AttachmentFile::upload($file)) - && $this->saveAttachment($id, $refid, $type)) - $uploaded[]=$id; - elseif($file['error']!=UPLOAD_ERR_NO_FILE) { - - // log file upload errors as interal notes + syslog debug. - if($file['error'] && gettype($file['error'])=='string') - $error = $file['error']; - else - $error ='Error #'.$file['error']; - - $this->logNote('File Upload Error', $error, 'SYSTEM', false); - - $ost->logDebug('File Upload Error (Ticket #'.$this->getExtId().')', $error); - } - - } - - return $uploaded; - } - - /* Wrapper or uploadAttachments - - used on client interface - - file type check is forced - - $_FILES is passed. - */ - function uploadFiles($files, $refid, $type) { - return $this->uploadAttachments(Format::files($files), $refid, $type, true); - } - - /* Emailed & API attachments handler */ - function importAttachments($attachments, $refid, $type, $checkFileTypes=true) { - global $ost; - - if(!$attachments || !is_array($attachments)) return null; - - $files = array(); - foreach ($attachments as &$info) { - //Do error checking... - if ($checkFileTypes && !$ost->isFileTypeAllowed($info)) - $info['error'] = 'Invalid file type (ext) for '.Format::htmlchars($info['name']); - elseif ($info['encoding'] && !strcasecmp($info['encoding'], 'base64')) { - if(!($info['data'] = base64_decode($info['data'], true))) - $info['error'] = sprintf('%s: Poorly encoded base64 data', Format::htmlchars($info['name'])); - } - - if($info['error']) { - $this->logNote('File Import Error', $info['error'], 'SYSTEM', false); - } elseif (($id=$this->saveAttachment($info, $refid, $type))) { - $files[] = $id; - } - } - - return $files; - } - - - /* - Save attachment to the DB. upload/import (above). - - @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); + function delete() { - return (db_query($sql) && ($id=db_insert_id()))?$id:0; - } - - - - function deleteAttachments(){ - - $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(); - - return $deleted; - } - - - function delete(){ - - $sql='DELETE FROM '.TICKET_TABLE.' WHERE ticket_id='.$this->getId().' LIMIT 1'; + $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())); - $this->deleteAttachments(); - + //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['name'] = array('type'=>'string', 'required'=>1, 'error'=>'Name required'); $fields['email'] = array('type'=>'email', 'required'=>1, 'error'=>'Valid email required'); @@ -1959,9 +1725,14 @@ function getIdByExtId($extId, $email=null) { } - + function lookup($id) { //Assuming local ID is the only lookup used! - return ($id && is_numeric($id) && ($ticket= new Ticket($id)) && $ticket->getId()==$id)?$ticket:null; + return ($id + && is_numeric($id) + && ($ticket= new Ticket($id)) + && $ticket->getId()==$id + && $ticket->getThread()) + ?$ticket:null; } function lookupByExtId($id, $email=null) { @@ -2241,8 +2012,11 @@ function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) { } $dept = $ticket->getDept(); + //post the message. - $msgid=$ticket->postMessage($vars , $source, false); + 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); // Configure service-level-agreement for this ticket $ticket->selectSLAId($vars['slaId']); @@ -2260,10 +2034,8 @@ function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) { # Messages that are clearly auto-responses from email systems should # not have a return 'ping' message - if ($autorespond && $vars['header'] && - TicketFilter::isAutoResponse(Mail_Parse::splitHeaders($vars['header']))) { + if ($autorespond && $message && $message->isAutoResponse()) $autorespond=false; - } //Don't auto respond to mailer daemons. if( $autorespond && @@ -2274,8 +2046,8 @@ function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) { //post canned auto-response IF any (disables new ticket auto-response). if ($vars['cannedResponseId'] - && $ticket->postCannedReply($vars['cannedResponseId'], $msgid, $autorespond)) { - $ticket->markUnAnswered(); //Leave the ticket as unanswred. + && $ticket->postCannedReply($vars['cannedResponseId'], $message->getId(), $autorespond)) { + $ticket->markUnAnswered(); //Leave the ticket as unanswred. $autorespond = false; } @@ -2285,7 +2057,7 @@ function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) { $autorespond=false; /***** See if we need to send some alerts ****/ - $ticket->onNewTicket($vars['message'], $autorespond, $alertstaff); + $ticket->onNewTicket($message, $autorespond, $alertstaff); /************ check if the user JUST reached the max. open tickets limit **********/ if($cfg->getMaxOpenTickets()>0 @@ -2306,25 +2078,25 @@ function open($vars, &$errors) { global $thisstaff,$cfg; 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['issue']) $errors['issue']='Summary of the issue required'; else $vars['message']=$vars['issue']; - if($vars['source'] && !in_array(strtolower($vars['source']),array('email','phone','other'))) - $errors['source']='Invalid source - '.Format::htmlchars($vars['source']); - if(!($ticket=Ticket::create($vars, $errors, 'staff', false, (!$vars['assignId'])))) return false; $vars['msgId']=$ticket->getLastMsgId(); - $respId = 0; - + // post response - if any - if($vars['response']) { + $response = null; + if($vars['response'] && $thisstaff->canPostReply()) { $vars['response'] = $ticket->replaceVars($vars['response']); - if(($respId=$ticket->postReply($vars, $errors, false))) { + if(($response=$ticket->postReply($vars, $errors, false))) { //Only state supported is closed on response if(isset($vars['ticket_state']) && $thisstaff->canCloseTickets()) $ticket->setState($vars['ticket_state']); @@ -2340,7 +2112,7 @@ function open($vars, &$errors) { } $ticket->reload(); - + if(!$cfg->notifyONNewStaffTicket() || !isset($vars['alertuser'])) return $ticket; //No alerts. @@ -2356,8 +2128,8 @@ function open($vars, &$errors) { if($tpl && ($msg=$tpl->getNewTicketNoticeMsgTemplate()) && $email) { $message = $vars['issue']; - if($vars['response']) - $message.="\n\n".$vars['response']; + if($response) + $message.="\n\n".$response->getBody(); if($vars['signature']=='mine') $signature=$thisstaff->getSignature(); @@ -2372,7 +2144,7 @@ function open($vars, &$errors) { if($cfg->stripQuotedReply() && ($tag=trim($cfg->getReplySeparator()))) $msg['body'] ="\n$tag\n\n".$msg['body']; - $attachments =($cfg->emailAttachments() && $respId)?$ticket->getAttachments($respId,'R'):array(); + $attachments =($cfg->emailAttachments() && $response)?$response->getAttachments():array(); $email->send($ticket->getEmail(), $msg['subj'], $msg['body'], $attachments); } diff --git a/include/client/view.inc.php b/include/client/view.inc.php index fa8a5b4a..6684413c 100644 --- a/include/client/view.inc.php +++ b/include/client/view.inc.php @@ -72,7 +72,9 @@    getAttachmentsLinks($entry['id'], $entry['thread_type']))) { ?> + if($entry['attachments'] + && ($tentry=$ticket->getThreadEntry($entry['id'])) + && ($links=$tentry->getAttachmentsLinks())) { ?> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 2a1a7aeb..45f25805 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -306,7 +306,9 @@ getAttachmentsLinks($note['id'],'N'))) {?> + if($note['attachments'] + && ($tentry=$ticket->getThreadEntry($note['id'])) + && ($links=$tentry->getAttachmentsLinks())) { ?> @@ -325,7 +327,10 @@ 'message','R'=>'response', 'N'=>'note'); /* -------- Messages & Responses & Notes (if inline)-------------*/ - if(($thread=$ticket->getThread($cfg->showNotesInline()))) { + $types = array('M', 'R'); + if($cfg->showNotesInline()) + $types[] = 'N'; + if(($thread=$ticket->getThreadEntries($types))) { foreach($thread as $entry) { ?> @@ -336,7 +341,9 @@ getAttachmentsLinks($entry['id'], $entry['thread_type']))) {?> + if($entry['attachments'] + && ($tentry=$ticket->getThreadEntry($entry['id'])) + && ($links=$tentry->getAttachmentsLinks())) {?> @@ -512,7 +519,7 @@
+ if($errors['postnote']) {?> diff --git a/scp/tickets.php b/scp/tickets.php index 9e436f20..5f0c418a 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -1,9 +1,9 @@ Copyright (c) 2006-2013 osTicket http://www.osticket.com @@ -46,23 +46,25 @@ $errors['err'] = 'Action denied. Contact admin for access'; else { - if(!$_POST['msgId']) - $errors['err']='Missing message ID - Internal error'; if(!$_POST['response']) $errors['response']='Response required'; - //Use locks to avoid double replies if($lock && $lock->getStaffId()!=$thisstaff->getId()) $errors['err']='Action Denied. Ticket is locked by someone else!'; - + //Make sure the email is not banned if(!$errors['err'] && TicketFilter::isBanned($ticket->getEmail())) $errors['err']='Email is in banlist. Must be removed to reply.'; } $wasOpen =($ticket->isOpen()); + //If no error...do the do. - if(!$errors && ($respId=$ticket->postReply($_POST, $errorsi, isset($_POST['emailreply'])))) { + $vars = $_POST; + if(!$errors && $_FILES['attachments']) + $vars['files'] = AttachmentFile::format($_FILES['attachments']); + + if(!$errors && ($response=$ticket->postReply($vars, $errors, isset($_POST['emailreply'])))) { $msg='Reply posted successfully'; $ticket->reload(); if($ticket->isClosed() && $wasOpen) @@ -73,7 +75,7 @@ } break; case 'transfer': /** Transfer ticket **/ - //Check permission + //Check permission if(!$thisstaff->canTransferTickets()) $errors['err']=$errors['transfer'] = 'Action Denied. You are not allowed to transfer tickets.'; else { @@ -85,13 +87,13 @@ $errors['deptId'] = 'Ticket already in the department'; elseif(!($dept=Dept::lookup($_POST['deptId']))) $errors['deptId'] = 'Unknown or invalid department'; - + //Transfer message - required. if(!$_POST['transfer_comments']) $errors['transfer_comments'] = 'Transfer comments required'; elseif(strlen($_POST['transfer_comments'])<5) $errors['transfer_comments'] = 'Transfer comments too short!'; - + //If no errors - them attempt the transfer. if(!$errors && $ticket->transfer($_POST['deptId'], $_POST['transfer_comments'])) { $msg = 'Ticket transferred successfully to '.$ticket->getDeptName(); @@ -112,7 +114,7 @@ else { $id = preg_replace("/[^0-9]/", "",$_POST['assignId']); - $claim = (is_numeric($_POST['assignId']) && $_POST['assignId']==$thisstaff->getId()); + $claim = (is_numeric($_POST['assignId']) && $_POST['assignId']==$thisstaff->getId()); if(!$_POST['assignId'] || !$id) $errors['assignId'] = 'Select assignee'; @@ -132,7 +134,7 @@ $errors['assign_comments'] = 'Assignment comments required'; elseif(strlen($_POST['assign_comments'])<5) $errors['assign_comments'] = 'Comment too short'; - + if(!$errors && $ticket->assign($_POST['assignId'], $_POST['assign_comments'], !$claim)) { if($claim) { $msg = 'Ticket is NOW assigned to you!'; @@ -146,7 +148,7 @@ $errors['assign'] = 'Correct the error(s) below and try again!'; } } - break; + break; case 'postnote': /* Post Internal Note */ //Make sure the staff can set desired state if($_POST['state']) { @@ -158,12 +160,22 @@ } $wasOpen = ($ticket->isOpen()); - if(($noteId=$ticket->postNote($_POST, $errors, $thisstaff))) { + + $vars = $_POST; + if($_FILES['attachments']) + $vars['files'] = AttachmentFile::format($_FILES['attachments']); + + if(($note=$ticket->postNote($vars, $errors, $thisstaff))) { + $msg='Internal note posted successfully'; if($wasOpen && $ticket->isClosed()) $ticket = null; //Going back to main listing. + } else { - $errors['err'] = 'Unable to post internal note - missing or invalid data.'; + + if(!$errors['err']) + $errors['err'] = 'Unable to post internal note - missing or invalid data.'; + $errors['postnote'] = 'Unable to post the note. Correct the error(s) below and try again!'; } break; @@ -195,9 +207,9 @@ $note = $_POST['ticket_status_notes']; else $note='Ticket closed (without comments)'; - + $ticket->logNote('Ticket Closed', $note, $thisstaff); - + //Going back to main listing. TicketLock::removeStaffLocks($thisstaff->getId(), $ticket->getId()); $page=$ticket=null; @@ -299,7 +311,7 @@ } elseif(Banlist::remove($ticket->getEmail())) { $msg = 'Email removed from banlist'; } elseif(!BanList::includes($ticket->getEmail())) { - $warn = 'Email is not in the banlist'; + $warn = 'Email is not in the banlist'; } else { $errors['err']='Unable to remove the email from banlist. Try again.'; } @@ -333,7 +345,7 @@ switch($_POST['a']) { case 'mass_process': if(!$thisstaff->canManageTickets()) - $errors['err']='You do not have permission to mass manage tickets. Contact admin for such access'; + $errors['err']='You do not have permission to mass manage tickets. Contact admin for such access'; elseif(!$_POST['tids'] || !is_array($_POST['tids'])) $errors['err']='No tickets selected. You must select at least one ticket.'; else { @@ -364,7 +376,7 @@ if($thisstaff->canCloseTickets()) { $note='Ticket closed without response by '.$thisstaff->getName(); foreach($_POST['tids'] as $k=>$v) { - if(($t=Ticket::lookup($v)) && $t->isOpen() && @$t->close()) { + if(($t=Ticket::lookup($v)) && $t->isOpen() && @$t->close()) { $i++; $t->logNote('Ticket Closed', $note, $thisstaff); } @@ -401,7 +413,7 @@ foreach($_POST['tids'] as $k=>$v) { if(($t=Ticket::lookup($v)) && @$t->delete()) $i++; } - + //Log a warning if($i) { $log = sprintf('%s (%s) just deleted %d ticket(s)', @@ -429,13 +441,19 @@ $ticket=null; if(!$thisstaff || !$thisstaff->canCreateTickets()) { $errors['err']='You do not have permission to create tickets. Contact admin for such access'; - }elseif(($ticket=Ticket::open($_POST, $errors))) { - $msg='Ticket created successfully'; - $_REQUEST['a']=null; - if(!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed()) - $ticket=null; - }elseif(!$errors['err']) { - $errors['err']='Unable to create the ticket. Correct the error(s) and try again'; + } else { + $vars = $_POST; + if($_FILES['attachments']) + $vars['files'] = AttachmentFile::format($_FILES['attachments']); + + if(($ticket=Ticket::open($vars, $errors))) { + $msg='Ticket created successfully'; + $_REQUEST['a']=null; + if(!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed()) + $ticket=null; + } elseif(!$errors['err']) { + $errors['err']='Unable to create the ticket. Correct the error(s) and try again'; + } } break; } @@ -470,7 +488,7 @@ 'title'=>'Answered Tickets', 'href'=>'tickets.php?status=answered', 'iconclass'=>'answeredTickets'), - ($_REQUEST['status']=='answered')); + ($_REQUEST['status']=='answered')); } } @@ -515,7 +533,7 @@ $nav->addSubMenu(array('desc'=>'New Ticket', 'href'=>'tickets.php?a=open', 'iconclass'=>'newTicket'), - ($_REQUEST['a']=='open')); + ($_REQUEST['a']=='open')); } @@ -524,7 +542,7 @@ $ost->setPageTitle('Ticket #'.$ticket->getNumber()); $nav->setActiveSubMenu(-1); $inc = 'ticket-view.inc.php'; - if($_REQUEST['a']=='edit' && $thisstaff->canEditTickets()) + if($_REQUEST['a']=='edit' && $thisstaff->canEditTickets()) $inc = 'ticket-edit.inc.php'; elseif($_REQUEST['a'] == 'print' && !$ticket->pdfExport($_REQUEST['psize'], $_REQUEST['notes'])) $errors['err'] = 'Internal error: Unable to export the ticket to PDF for print.';