function createTicket($mid) { global $ost; if (!($mailinfo = $this->getHeaderInfo($mid))) { return false; } // TODO: If the content-type of the message is 'message/rfc822', // then this is a message with the forwarded message as the // attachment. Download the body and pass it along to the mail // parsing engine. $info = Mail_Parse::splitHeaders($mailinfo['header']); if (strtolower($info['Content-Type']) == 'message/rfc822') { if ($wrapped = $this->getPart($mid, 'message/rfc822')) { require_once INCLUDE_DIR . 'api.tickets.php'; // Simulate piping the contents into the system $api = new TicketApiController(); $parser = new EmailDataParser(); if ($data = $parser->parse($wrapped)) { return $api->processEmail($data); } } // If any of this fails, create the ticket as usual } //Is the email address banned? if ($mailinfo['email'] && TicketFilter::isBanned($mailinfo['email'])) { //We need to let admin know... $ost->logWarning(_S('Ticket denied'), sprintf(_S('Banned email — %s'), $mailinfo['email']), false); return true; //Report success (moved or delete) } // Parse MS TNEF emails if (($struct = imap_fetchstructure($this->mbox, $mid)) && ($attachments = $this->getAttachments($struct))) { foreach ($attachments as $i => $info) { if (0 === strcasecmp('application/ms-tnef', $info['type'])) { try { $data = $this->decode(imap_fetchbody($this->mbox, $mid, $info['index']), $info['encoding']); $tnef = new TnefStreamParser($data); $this->tnef = $tnef->getMessage(); // No longer considered an attachment unset($attachments[$i]); // There should only be one of these break; } catch (TnefException $ex) { // Noop -- winmail.dat remains an attachment } } } } $vars = $mailinfo; $vars['name'] = $mailinfo['name']; $vars['subject'] = $mailinfo['subject'] ?: '[No Subject]'; $vars['emailId'] = $mailinfo['emailId'] ?: $this->getEmailId(); $vars['to-email-id'] = $mailinfo['emailId'] ?: 0; $vars['flags'] = new ArrayObject(); if ($this->isBounceNotice($mid)) { // Fetch the original References and assign to 'references' if ($headers = $this->getOriginalMessageHeaders($mid)) { $vars['references'] = $headers['references']; $vars['in-reply-to'] = @$headers['in-reply-to'] ?: null; } // Fetch deliver status report $vars['message'] = $this->getDeliveryStatusMessage($mid) ?: $this->getBody($mid); $vars['thread-type'] = 'N'; $vars['flags']['bounce'] = true; } else { $vars['message'] = $this->getBody($mid); $vars['flags']['bounce'] = TicketFilter::isBounce($info); } //Missing FROM name - use email address. if (!$vars['name']) { list($vars['name']) = explode('@', $vars['email']); } if ($ost->getConfig()->useEmailPriority()) { $vars['priorityId'] = $this->getPriority($mid); } $ticket = null; $newticket = true; $errors = array(); $seen = false; // Use the settings on the thread entry on the ticket details // form to validate the attachments in the email $tform = TicketForm::objects()->one()->getForm(); $messageField = $tform->getField('message'); $fileField = $messageField->getWidget()->getAttachments(); // Fetch attachments if any. if ($messageField->isAttachmentsEnabled()) { // Include TNEF attachments in the attachments list if ($this->tnef) { foreach ($this->tnef->attachments as $at) { $attachments[] = array('cid' => @$at->AttachContentId ?: false, 'data' => $at, 'size' => @$at->DataSize ?: null, 'type' => @$at->AttachMimeTag ?: false, 'name' => $at->getName()); } } $vars['attachments'] = array(); foreach ($attachments as $a) { $file = array('name' => $a['name'], 'type' => $a['type']); if (@$a['data'] instanceof TnefAttachment) { $file['data'] = $a['data']->getData(); } else { // only fetch the body if necessary $self = $this; $file['data'] = function () use($self, $mid, $a) { return $self->decode(imap_fetchbody($self->mbox, $mid, $a['index']), $a['encoding']); }; } // Include the Content-Id if specified (for inline images) $file['cid'] = isset($a['cid']) ? $a['cid'] : false; // Validate and save immediately try { $file['id'] = $fileField->uploadAttachment($file); } catch (FileUploadError $ex) { $file['error'] = $file['name'] . ': ' . $ex->getMessage(); } $vars['attachments'][] = $file; } } // Allow signal handlers to interact with the message decoding Signal::send('mail.processed', $this, $vars); $seen = false; if (($thread = ThreadEntry::lookupByEmailHeaders($vars, $seen)) && ($t = $thread->getTicket()) && ($vars['staffId'] || !$t->isClosed() || $t->isReopenable()) && ($message = $thread->postEmail($vars))) { if (!$message instanceof ThreadEntry) { // Email has been processed previously return $message; } $ticket = $message->getTicket(); } elseif ($seen) { // Already processed, but for some reason (like rejection), no // thread item was created. Ignore the email return true; } elseif ($ticket = Ticket::create($vars, $errors, 'Email')) { $message = $ticket->getLastMessage(); } else { //Report success if the email was absolutely rejected. if (isset($errors['errno']) && $errors['errno'] == 403) { // Never process this email again! ThreadEntry::logEmailHeaders(0, $vars['mid']); return true; } // Log an error to the system logs $mailbox = Email::lookup($vars['emailId']); $ost->logError(_S('Mail Processing Exception'), sprintf(_S("Mailbox: %s | Error(s): %s"), $mailbox->getEmail(), print_r($errors, true)), false); // Indicate failure of mail processing return null; } return $ticket; }
function create($vars, &$errors, $origin, $autorespond = true, $alertstaff = true) { global $cfg, $thisclient, $_FILES; //Make sure the email is not banned if ($vars['email'] && EmailFilter::isBanned($vars['email'])) { $errors['err'] = 'Ticket denied. Error #403'; Sys::log(LOG_WARNING, 'Ticket denied', 'Banned email - ' . $vars['email']); return 0; } $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'); $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['origin'] = 'Invalid origin given'; } $fields['pri'] = array('type' => 'int', 'required' => 0, 'error' => 'Invalid Priority'); $fields['phone'] = array('type' => 'phone', 'required' => 0, 'error' => 'Valid phone # required'); 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'; } } //check attachment..if any is set ...only set on webbased tickets.. //XXX:?? Create ticket anyway and simply drop the attachments?? We're already doing so with emails. if ($_FILES['attachment']['name'] && $cfg->allowOnlineAttachments()) { if (!$cfg->canUploadFileType($_FILES['attachment']['name'])) { $errors['attachment'] = 'Invalid file type [ ' . Format::htmlchars($_FILES['attachment']['name']) . ' ]'; } elseif ($_FILES['attachment']['size'] > $cfg->getMaxFileSize()) { $errors['attachment'] = 'File is too big. Max ' . $cfg->getMaxFileSize() . ' bytes allowed'; } } # Perform email filter actions on the new ticket arguments XXX: Move filter to the top and check for reject... if (!$errors && ($ef = new EmailFilter($vars))) { $ef->apply($vars); } # Some things will need to be unpacked back into the scope of this # function if (isset($vars['autorespond'])) { $autorespond = $vars['autorespond']; } //check ticket limits..if limit set is >0 //TODO: Base ticket limits on SLA... XXX: move it elsewhere?? if ($vars['email'] && !$errors && $cfg->getMaxOpenTickets() > 0 && strcasecmp($origin, 'staff')) { $openTickets = Ticket::getOpenTicketsByEmail($vars['email']); if ($openTickets >= $cfg->getMaxOpenTickets()) { $errors['err'] = "You've reached the maximum open tickets allowed."; //Send the notice only once (when the limit is reached) incase of autoresponders at client end. if ($cfg->getMaxOpenTickets() == $openTickets && $cfg->sendOverlimitNotice()) { if ($vars['deptId']) { $dept = Dept::lookup($vars['deptId']); } if (!$dept || !($tpl = $dept->getTemplate())) { $tpl = $cfg->getDefaultTemplate(); } if (!$dept || !($email = $dept->getAutoRespEmail())) { $email = $cfg->getDefaultEmail(); } if ($tpl && ($msg = $tpl->getOverlimitMsgTemplate()) && $email) { $body = str_replace('%name', $vars['name'], $msg['body']); $body = str_replace('%email', $vars['email'], $msg['body']); $body = str_replace('%url', $cfg->getBaseUrl(), $body); $body = str_replace('%signature', $dept && $dept->isPublic() ? $dept->getSignature() : '', $body); $email->send($vars['email'], $msg['subj'], $body); } //Log + Alert admin...this might be spammy (no option to disable)...but it is helpful..I think. $msg = 'Support ticket request denied for ' . $vars['email'] . "\n" . 'Open ticket:' . $openTickets . "\n" . 'Max Allowed:' . $cfg->getMaxOpenTickets() . "\n\nNotice only sent once"; Sys::log(LOG_CRIT, 'Overlimit Notice', $msg); } } } //Any error above is fatal. if ($errors) { return 0; } // OK...just do it. $deptId = $vars['deptId']; //pre-selected Dept if any. $priorityId = $vars['pri']; $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); //TODO: recover from postMessage error?? //Upload attachments...web based. - XXX: Assumes user uploaded attachments!! XXX: move it to client interface. if ($_FILES['attachment']['name'] && $cfg->allowOnlineAttachments() && $msgid) { if (!$cfg->allowAttachmentsOnlogin() || $cfg->allowAttachmentsOnlogin() && ($thisuser && $thisuser->isValid())) { $ticket->uploadAttachment($_FILES['attachment'], $msgid, 'M'); } } // 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; } /***** See if we need to send some alerts ****/ $ticket->onNewTicket($vars['message'], $autorespond, $alertstaff); return $ticket; }
function _build($lang) { list($code, $zip) = $this->_request("download/$lang.zip"); if ($code !== 200) $this->fail('Language is not available'."\n"); $temp = tempnam('/tmp', 'osticket-cli'); $f = fopen($temp, 'w'); fwrite($f, $zip); fclose($f); $zip = new ZipArchive(); $zip->open($temp); unlink($temp); $lang = str_replace('-','_',$lang); @unlink(I18N_DIR."$lang.phar"); $phar = new Phar(I18N_DIR."$lang.phar"); $phar->startBuffering(); $po_file = false; for ($i=0; $i<$zip->numFiles; $i++) { $info = $zip->statIndex($i); $contents = $zip->getFromIndex($i); if (!$contents) continue; if (strpos($info['name'], '/messages.po') !== false) { $po_file = $contents; // Don't add the PO file as-is to the PHAR file continue; } $phar->addFromString($info['name'], $contents); } // TODO: Add i18n extras (like fonts) // Redactor language pack // $langs = array($lang); if (strpos($lang, '_') !== false) { @list($short) = explode('_', $lang); $langs[] = $short; } foreach ($langs as $l) { list($code, $js) = $this->_http_get( 'http://imperavi.com/webdownload/redactor/lang/?lang=' .strtolower($l)); if ($code == 200 && ($js != 'File not found')) { $phar->addFromString('js/redactor.js', $js); break; } } if ($code != 200) $this->stderr->write($lang . ": Unable to fetch Redactor language file\n"); // JQuery UI Datepicker // http://jquery-ui.googlecode.com/svn/tags/latest/ui/i18n/jquery.ui.datepicker-de.js foreach ($langs as $l) { list($code, $js) = $this->_http_get( 'http://jquery-ui.googlecode.com/svn/tags/latest/ui/i18n/jquery.ui.datepicker-' .str_replace('_','-',$l).'.js'); // If locale-specific version is not available, use the base // language version (de if de_CH is not available) if ($code == 200) break; } if ($code == 200) $phar->addFromString('js/jquery.ui.datepicker.js', $js); else $this->stderr->write(str_replace('_','-',$lang) .": Unable to fetch jQuery UI Datepicker locale file\n"); // True type fonts for PDF printing $langs = (include I18N_DIR . 'langs.php'); $info = $langs[$lang]; if (isset($info['fonts'])) { foreach ($info['fonts'] as $name => $types) { foreach ($types as $code => $fullname) { list($ttf, $url) = $fullname; if (!$url) continue; list($code, $file) = $this->_http_get($url); if ($code == 200) $phar->addFromString('fonts/'.$ttf, $file); else $this->stderr->write( "*** Unable to fetch $url\n"); } } } // Add in the messages.mo.php file if ($po_file) { $pipes = array(); $msgfmt = proc_open('msgfmt -o- -', array(0=>array('pipe','r'), 1=>array('pipe','w')), $pipes); if (is_resource($msgfmt)) { fwrite($pipes[0], $po_file); fclose($pipes[0]); $mo_input = fopen('php://temp', 'r+b'); fwrite($mo_input, stream_get_contents($pipes[1])); rewind($mo_input); require_once INCLUDE_DIR . 'class.translation.php'; $mo = Translation::buildHashFile($mo_input, false, true); $phar->addFromString('LC_MESSAGES/messages.mo.php', $mo); fclose($mo_input); } } // Add in translation of javascript strings $phrases = array(); if ($mo && ($js = $this->__getAllJsPhrases())) { $mo = (eval (substr($mo, 5))); # Chop off <?php foreach ($js as $c) { foreach ($c['forms'] as $f) { $phrases[$f] = @$mo[$f] ?: $f; } } $phar->addFromString( 'js/osticket-strings.js', sprintf('(function($){$.oststrings=%s;})(jQuery);', JsonDataEncoder::encode($phrases)) ); } // Include a manifest include_once INCLUDE_DIR . 'class.mailfetch.php'; $po_header = Mail_Parse::splitHeaders($mo['']); $info = array( 'Build-Date' => date(DATE_RFC822), 'Build-Version' => trim(`git describe`), 'Language' => $po_header['Language'], #'Phrases' => #'Translated' => #'Approved' => 'Id' => 'lang:' . $lang, 'Last-Revision' => $po_header['PO-Revision-Date'], 'Version' => (int)(strtotime($po_header['PO-Revision-Date']) / 10000), ); $phar->addFromString( 'MANIFEST.php', sprintf('<?php return %s;', var_export($info, true))); // TODO: Sign files // Use a very small stub $phar->setStub('<?php __HALT_COMPILER();'); $phar->setSignatureAlgorithm(Phar::SHA1); $phar->stopBuffering(); }
static function isBounce($headers) { if ($headers && !is_array($headers)) { $headers = Mail_Parse::splitHeaders($headers); } $bounce_headers = array('From' => array('stripos', array('MAILER-DAEMON', '<>'), null, false), 'Subject' => array('stripos', array('DELIVERY FAILURE', 'DELIVERY STATUS', 'UNDELIVERABLE:', 'Undelivered Mail Returned'), 0), 'Return-Path' => array('strcmp', array('<>'), 0), 'Content-Type' => array('stripos', array('report-type=delivery-status'), null, false), 'X-Failed-Recipients' => array('strpos', array('@'), null, false)); foreach ($bounce_headers as $header => $find) { if (!isset($headers[$header])) { continue; } @(list($func, $searches, $pos, $neg) = $find); if (!($value = $headers[$header]) || !is_array($searches)) { continue; } foreach ($searches as $f) { $result = call_user_func($func, $value, $f); if ($pos === null && $result !== $neg or $result === $pos) { return true; } } } return false; }
function create($vars, &$errors, $origin, $autorespond = true, $alertstaff = true) { global $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'; Sys::log(LOG_WARNING, '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()) && $opentickets >= $cfg->getMaxOpenTickets()) { $errors['err'] = "You've reached the maximum open tickets allowed."; Sys::log(LOG_WARNING, 'Ticket denied -' . $vars['email'], sprintf('Max open tickets (%d) reached for %s ', $cfg->getMaxOpenTickets(), $vars['email'])); return 0; } } // Make sure email contents should not be rejected if (($email_filter = new EmailFilter($vars)) && ($filter = $email_filter->shouldReject())) { $errors['err'] = 'Ticket denied. Error #403'; Sys::log(LOG_WARNING, 'Ticket denied', sprintf('Banned email - %s by filter "%s"', $vars['email'], $filter->getName())); return 0; } $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'); $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'); 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); } # 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. $priorityId = $vars['priorityId']; $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; } /***** See if we need to send some alerts ****/ $ticket->onNewTicket($vars['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')); } /* Phew! ... time for tea (KETEPA) */ return $ticket; }
function getEmailHeaderArray() { require_once INCLUDE_DIR . 'class.mailparse.php'; if (!isset($this->ht['@headers'])) { $this->ht['@headers'] = Mail_Parse::splitHeaders($this->ht['headers']); } return $this->ht['@headers']; }
function getEmailHeaders() { require_once INCLUDE_DIR . 'class.mailparse.php'; $sql = 'SELECT headers FROM ' . TICKET_EMAIL_INFO_TABLE . ' WHERE message_id=' . $this->getId(); $headers = db_result(db_query($sql)); return Mail_Parse::splitHeaders($headers); }
function isAutoBounce($headers) { if ($headers && !is_array($headers)) { $headers = Mail_Parse::splitHeaders($headers); } $bounce_headers = array('From' => array('<MAILER-DAEMON@MAILER-DAEMON>', 'MAILER-DAEMON', '<>'), 'Subject' => array('DELIVERY FAILURE', 'DELIVERY STATUS', 'UNDELIVERABLE:')); foreach ($bounce_headers as $header => $find) { if (!isset($headers[$header])) { continue; } $value = strtoupper($headers[$header]); if (is_array($find)) { foreach ($find as $f) { if (strpos($value, $f) === 0) { return true; } } } elseif (strpos($value, $find) === 0) { return true; } } return false; }