function isbanned($email) { return TicketFilter::isBanned($email); }
//Assigned team. $sla = $ticket->getSLA(); $lock = $ticket->getLock(); //Ticket lock obj $id = $ticket->getId(); //Ticket ID. //Useful warnings and errors the user might want to know! if ($ticket->isClosed() && !$ticket->isReopenable()) { $warn = sprintf(__('Current ticket status (%s) does not allow the end user to reply.'), $ticket->getStatus()); } elseif ($ticket->isAssigned() && ($staff && $staff->getId() != $thisstaff->getId() || $team && !$team->hasMember($thisstaff))) { $warn .= sprintf(' <span class="Icon assignedTicket">%s</span>', sprintf(__('Ticket is assigned to %s'), implode('/', $ticket->getAssignees()))); } if (!$errors['err']) { if ($lock && $lock->getStaffId() != $thisstaff->getId()) { $errors['err'] = sprintf(__('This ticket is currently locked by %s'), $lock->getStaffName()); } elseif ($emailBanned = TicketFilter::isBanned($ticket->getEmail())) { $errors['err'] = __('Email is in banlist! Must be removed before any reply/response'); } // elseif (!Validator::is_valid_email($ticket->getEmail())) // $errors['err'] = __('EndUser email address is not valid! Consider updating it before responding'); } $unbannable = $emailBanned ? BanList::includes($ticket->getEmail()) : false; if ($ticket->isOverdue()) { $warn .= ' <span class="Icon overdueTicket">' . __('Marked overdue!') . '</span>'; } ?> <table width="940" cellpadding="2" cellspacing="0" border="0"> <tr> <td width="20%" class="has_bottom_border"> <h2><a href="tickets.php?id=<?php echo $ticket->getId();
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; }
$lock = $ticket->getLock(); //Ticket lock if any switch (strtolower($_POST['a'])) { case 'reply': if (!$thisstaff->canPostReply()) { $errors['err'] = __('Action denied. Contact admin for access'); } else { 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.'); } } //If no error...do the do. $vars = $_POST; $vars['cannedattachments'] = $response_form->getField('attachments')->getClean(); if (!$errors && ($response = $ticket->postReply($vars, $errors, $_POST['emailreply']))) { $msg = sprintf(__('%s: Reply posted successfully'), sprintf(__('Ticket #%s'), sprintf('<a href="tickets.php?id=%d"><b>%s</b></a>', $ticket->getId(), $ticket->getNumber()))); // Clear attachment list $response_form->setSource(array()); $response_form->getField('attachments')->reset(); // Remove staff's locks TicketLock::removeStaffLocks($thisstaff->getId(), $ticket->getId()); // Cleanup response draft for this user Draft::deleteForNamespace('ticket.response.' . $ticket->getId(), $thisstaff->getId());
protected function filterTicketData($origin, $vars, $forms, $user = false) { global $cfg; // Unset all the filter data field data in case things change // during recursive calls foreach ($vars as $k => $v) { if (strpos($k, 'field.') === 0) { unset($vars[$k]); } } foreach ($forms as $F) { if ($F) { $vars += $F->getFilterData(); } } if (!$user) { $interesting = array('name', 'email'); $user_form = UserForm::getUserForm()->getForm($vars); // Add all the user-entered info for filtering foreach ($interesting as $F) { $field = $user_form->getField($F); $vars[$F] = $field->toString($field->getClean()); } // Attempt to lookup the user and associated data $user = User::lookupByEmail($vars['email']); } // Add in user and organization data for filtering if ($user) { $vars += $user->getFilterData(); $vars['email'] = $user->getEmail(); $vars['name'] = $user->getName()->getOriginal(); if ($org = $user->getOrganization()) { $vars += $org->getFilterData(); } } else { // Unpack all known user info from the request foreach ($user_form->getFields() as $f) { $vars['field.' . $f->get('id')] = $f->toString($f->getClean()); } // Add in organization data if one exists for this email domain list($mailbox, $domain) = explode('@', $vars['email'], 2); if ($org = Organization::forDomain($domain)) { $vars += $org->getFilterData(); } } try { // Make sure the email address is not banned if (TicketFilter::isBanned($vars['email'])) { throw new RejectedException(Banlist::getFilter(), $vars); } // Init ticket filters... $ticket_filter = new TicketFilter($origin, $vars); $ticket_filter->apply($vars); } catch (FilterDataChanged $ex) { // Don't pass user recursively, assume the user has changed return self::filterTicketData($origin, $ex->getData(), $forms); } return $vars; }
static function create($vars, &$errors, $origin, $autorespond = true, $alertstaff = true) { global $ost, $cfg, $thisclient, $_FILES; // Don't enforce form validation for email $field_filter = function ($type) use($origin) { return function ($f) use($origin, $type) { // Ultimately, only offer validation errors for web for // non-internal fields. For email, no validation can be // performed. For other origins, validate as usual switch (strtolower($origin)) { case 'email': return false; case 'staff': // Required 'Contact Information' fields aren't required // when staff open tickets return $type != 'user' || in_array($f->get('name'), array('name', 'email')); case 'web': return !$f->get('private'); default: return true; } }; }; $reject_ticket = function ($message) use(&$errors) { global $ost; $errors = array('errno' => 403, 'err' => 'This help desk is for use by authorized users only'); $ost->logWarning('Ticket Denied', $message, false); return 0; }; // Create and verify the dynamic form entry for the new ticket $form = TicketForm::getNewInstance(); // If submitting via email, ensure we have a subject and such foreach ($form->getFields() as $field) { $fname = $field->get('name'); if ($fname && isset($vars[$fname]) && !$field->value) { $field->value = $field->parse($vars[$fname]); } } if (!$form->isValid($field_filter('ticket'))) { $errors += $form->errors(); } // Unpack dynamic variables into $vars for filter application $vars += $form->getFilterData(); // Unpack the basic user information if ($vars['uid'] && ($user = User::lookup($vars['uid']))) { $vars['email'] = $user->getEmail(); $vars['name'] = $user->getName(); // Add in user and organization data for filtering $vars += $user->getFilterData(); if ($org = $user->getOrganization()) { $vars += $org->getFilterData(); } } else { $interesting = array('name', 'email'); $user_form = UserForm::getUserForm()->getForm($vars); // Add all the user-entered info for filtering foreach ($user_form->getFields() as $f) { $vars['field.' . $f->get('id')] = $f->toString($f->getClean()); if (in_array($f->get('name'), $interesting)) { $vars[$f->get('name')] = $vars['field.' . $f->get('id')]; } } // Add in organization data if one exists for this email domain list($mailbox, $domain) = explode('@', $vars['email'], 2); if ($org = Organization::forDomain($domain)) { $vars += $org->getFilterData(); } } //Check for 403 if ($vars['email'] && Validator::is_email($vars['email'])) { //Make sure the email address is not banned if (TicketFilter::isBanned($vars['email'])) { return $reject_ticket('Banned email - ' . $vars['email']); } //Make sure the open ticket limit hasn't been reached. (LOOP CONTROL) if ($cfg->getMaxOpenTickets() > 0 && strcasecmp($origin, 'staff') && ($_user = TicketUser::lookupByEmail($vars['email'])) && ($openTickets = $_user->getNumOpenTickets()) && $openTickets >= $cfg->getMaxOpenTickets()) { $errors = array('err' => "You've reached the maximum open tickets allowed."); $ost->logWarning('Ticket denied -' . $vars['email'], sprintf('Max open tickets (%d) reached for %s ', $cfg->getMaxOpenTickets(), $vars['email']), false); return 0; } } //Init ticket filters... $ticket_filter = new TicketFilter($origin, $vars); // Make sure email contents should not be rejected if ($ticket_filter && ($filter = $ticket_filter->shouldReject())) { return $reject_ticket(sprintf('Ticket rejected ( %s) by filter "%s"', $vars['email'], $filter->getName())); } if ($vars['topicId'] && ($topic = Topic::lookup($vars['topicId']))) { if ($topic_form = $topic->getForm()) { $TF = $topic_form->getForm($vars); $topic_form = $topic_form->instanciate(); $topic_form->setSource($vars); if (!$TF->isValid($field_filter('topic'))) { $errors = array_merge($errors, $TF->errors()); } } } $id = 0; $fields = array(); $fields['message'] = array('type' => '*', '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' => 0, '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'; } if (!Validator::process($fields, $vars, $errors) && !$errors['err']) { $errors['err'] = 'Missing or invalid data - check the errors and try again'; } if ($vars['topicId'] && !$topic) { $errors['topicId'] = 'Invalid help topic selected'; } //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'; } } if (!$errors) { # Perform ticket filter actions on the new ticket arguments if ($ticket_filter) { $ticket_filter->apply($vars); } // Allow vars to be changed in ticket filter and applied to the user // account created or detected if (!$user && $vars['email']) { $user = User::lookupByEmail($vars['email']); } if (!$user) { // Reject emails if not from registered clients (if // configured) if (strcasecmp($origin, 'email') === 0 && !$cfg->acceptUnregisteredEmail()) { list($mailbox, $domain) = explode('@', $vars['email'], 2); // Users not yet created but linked to an organization // are still acceptable if (!Organization::forDomain($domain)) { return $reject_ticket(sprintf('Ticket rejected (%s) (unregistered client)', $vars['email'])); } } $user_form = UserForm::getUserForm()->getForm($vars); if (!$user_form->isValid($field_filter('user')) || !($user = User::fromVars($user_form->getClean()))) { $errors['user'] = '******'; } } } // Any error above is fatal. if ($errors) { return 0; } # Some things will need to be unpacked back into the scope of this # function if (isset($vars['autorespond'])) { $autorespond = $vars['autorespond']; } # Apply filter-specific priority if ($vars['priorityId']) { $form->setAnswer('priority', null, $vars['priorityId']); } // If the filter specifies a help topic which has a form associated, // and there was previously either no help topic set or the help // topic did not have a form, there's no need to add it now as (1) // validation is closed, (2) there may be a form already associated // and filled out from the original help topic, and (3) staff // members can always add more forms now // OK...just do it. $deptId = $vars['deptId']; //pre-selected Dept if any. $source = ucfirst($vars['source']); // Apply email settings for emailed tickets. Email settings should // trump help topic settins if the email has an associated help // topic if ($vars['emailId'] && ($email = Email::lookup($vars['emailId']))) { $deptId = $deptId ?: $email->getDeptId(); $priority = $form->getAnswer('priority'); if (!$priority || !$priority->getIdValue()) { $form->setAnswer('priority', null, $email->getPriorityId()); } if ($autorespond) { $autorespond = $email->autoRespond(); } if (!isset($topic) && ($T = $email->getTopic()) && $T->isActive()) { $topic = $T; } $email = null; $source = 'Email'; } if (!isset($topic)) { // This may return NULL, no big deal $topic = $cfg->getDefaultTopic(); } // Intenal mapping magic...see if we need to override anything if (isset($topic)) { $deptId = $deptId ?: $topic->getDeptId(); $priority = $form->getAnswer('priority'); if (!$priority || !$priority->getIdValue()) { $form->setAnswer('priority', null, $topic->getPriorityId()); } if ($autorespond) { $autorespond = $topic->autoRespond(); } //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'] ?: $cfg->getDefaultSLAId(); } elseif ($topic && $topic->getSLAId()) { $vars['slaId'] = $topic->getSLAId(); } } // Auto assignment to organization account manager if (($org = $user->getOrganization()) && $org->autoAssignAccountManager() && ($code = $org->getAccountManagerId())) { if (!isset($vars['staffId']) && $code[0] == 's') { $vars['staffId'] = substr($code, 1); } elseif (!isset($vars['teamId']) && $code[0] == 't') { $vars['teamId'] = substr($code, 1); } } // Last minute checks $priority = $form->getAnswer('priority'); if (!$priority || !$priority->getIdValue()) { $form->setAnswer('priority', null, $cfg->getDefaultPriorityId()); } $deptId = $deptId ?: $cfg->getDefaultDeptId(); $topicId = isset($topic) ? $topic->getId() : 0; $ipaddress = $vars['ip'] ?: $_SERVER['REMOTE_ADDR']; $source = $source ?: 'Web'; //We are ready son...hold on to the rails. $number = Ticket::genRandTicketNumber(); $sql = 'INSERT INTO ' . TICKET_TABLE . ' SET created=NOW() ' . ' ,lastmessage= NOW()' . ' ,user_id=' . db_input($user->getId()) . ' ,`number`=' . db_input($number) . ' ,dept_id=' . db_input($deptId) . ' ,topic_id=' . db_input($topicId) . ' ,ip_address=' . db_input($ipaddress) . ' ,source=' . db_input($source); if (isset($vars['emailId']) && $vars['emailId']) { $sql .= ', email_id=' . db_input($vars['emailId']); } //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 ------------------------ */ if (!$cfg->useRandomIds()) { //Sequential ticket number support really..really suck arse. //To make things really easy we are going to use autoincrement ticket_id. db_query('UPDATE ' . TICKET_TABLE . ' SET `number`=' . db_input($id) . ' 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] } // Save the (common) dynamic form $form->setTicketId($id); $form->save(); // Save the form data from the help-topic form, if any if ($topic_form) { $topic_form->setTicketId($id); $topic_form->save(); } $ticket->loadDynamicData(); $dept = $ticket->getDept(); // Add organizational collaborators if ($org && $org->autoAddCollabs()) { $pris = $org->autoAddPrimaryContactsAsCollabs(); $members = $org->autoAddMembersAsCollabs(); $settings = array('isactive' => true); $collabs = array(); foreach ($org->allMembers() as $u) { if ($members || $pris && $u->isPrimaryContact()) { if ($c = $ticket->addCollaborator($u, $settings, $errors)) { $collabs[] = (string) $c; } } } //TODO: Can collaborators add others? if ($collabs) { //TODO: Change EndUser to name of user. $ticket->logNote(sprintf('Collaborators for %s organization added', $org->getName()), implode("<br>", $collabs), $org->getName(), false); } } //post the message. unset($vars['cannedattachments']); //Ticket::open() might have it set as part of open & respond. $vars['title'] = $vars['subject']; //Use the initial subject as title of the post. $vars['userId'] = $ticket->getUserId(); $message = $ticket->postMessage($vars, $origin, false); // Configure service-level-agreement for this ticket $ticket->selectSLAId($vars['slaId']); // Assign ticket to staff or team (new ticket by staff) if ($vars['assignId']) { $ticket->assign($vars['assignId'], $vars['note']); } else { // Auto assign staff or team - auto assignment based on filter // rules. Both team and staff can be assigned if ($vars['staffId']) { $ticket->assignToStaff($vars['staffId'], 'Auto Assignment'); } if ($vars['teamId']) { $ticket->assignToTeam($vars['teamId'], 'Auto Assignment'); } } /********** double check auto-response ************/ //Override auto responder if the FROM email is one of the internal emails...loop control. if ($autorespond && Email::getIdByEmail($ticket->getEmail())) { $autorespond = false; } # Messages that are clearly auto-responses from email systems should # not have a return 'ping' message if (isset($vars['flags']) && $vars['flags']['bounce']) { $autorespond = false; } if ($autorespond && $message->isAutoReply()) { $autorespond = false; } //post canned auto-response IF any (disables new ticket auto-response). if ($vars['cannedResponseId'] && $ticket->postCannedReply($vars['cannedResponseId'], $message->getId(), $autorespond)) { $ticket->markUnAnswered(); //Leave the ticket as unanswred. $autorespond = false; } //Check department's auto response settings // XXX: Dept. setting doesn't affect canned responses. if ($autorespond && $dept && !$dept->autoRespONNewTicket()) { $autorespond = false; } //Don't send alerts to staff when the message is a bounce // this is necessary to avoid possible loop (especially on new ticket) if ($alertstaff && $message->isBounce()) { $alertstaff = false; } /***** See if we need to send some alerts ****/ $ticket->onNewTicket($message, $autorespond, $alertstaff); /************ check if the user JUST reached the max. open tickets limit **********/ if ($cfg->getMaxOpenTickets() > 0 && ($user = $ticket->getOwner()) && $user->getNumOpenTickets() == $cfg->getMaxOpenTickets()) { $ticket->onOpenLimit($autorespond && strcasecmp($origin, 'staff')); } /* Start tracking ticket lifecycle events */ $ticket->logEvent('created'); /* Phew! ... time for tea (KETEPA) */ return $ticket; }
function createTicket($mid) { global $ost; if (!($mailinfo = $this->getHeaderInfo($mid))) { return false; } //Is the email address banned? if ($mailinfo['email'] && TicketFilter::isBanned($mailinfo['email'])) { //We need to let admin know... $ost->logWarning('Ticket denied', 'Banned email - ' . $mailinfo['email'], false); return true; //Report success (moved or delete) } $vars = $mailinfo; $vars['name'] = $this->mime_decode($mailinfo['name']); $vars['subject'] = $mailinfo['subject'] ? $this->mime_decode($mailinfo['subject']) : '[No Subject]'; $vars['message'] = Format::stripEmptyLines($this->getBody($mid)); $vars['emailId'] = $mailinfo['emailId'] ? $mailinfo['emailId'] : $this->getEmailId(); //Missing FROM name - use email address. if (!$vars['name']) { $vars['name'] = $vars['email']; } //An email with just attachments can have empty body. if (!$vars['message']) { $vars['message'] = '-'; } if ($ost->getConfig()->useEmailPriority()) { $vars['priorityId'] = $this->getPriority($mid); } $ticket = null; $newticket = true; $errors = array(); $seen = false; if (($thread = ThreadEntry::lookupByEmailHeaders($vars, $seen)) && ($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; } # check if it's a bounce! if ($vars['header'] && TicketFilter::isAutoBounce($vars['header'])) { $ost->logWarning('Bounced email', $vars['message'], false); return true; } //TODO: Log error.. return null; } //Save attachments if any. if ($message && $ost->getConfig()->allowEmailAttachments() && ($struct = imap_fetchstructure($this->mbox, $mid)) && ($attachments = $this->getAttachments($struct))) { foreach ($attachments as $a) { $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); } } return $ticket; }
function create($vars, &$errors, $origin, $autorespond = true, $alertstaff = true) { global $ost, $cfg, $thisclient, $_FILES; // Drop extra whitespace foreach (array('email', 'phone', 'subject', 'name') 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'; $errors['errno'] = 403; $ost->logWarning('Ticket denied', 'Banned email - ' . $vars['email']); return 0; } //Make sure the open ticket limit hasn't been reached. (LOOP CONTROL) if ($cfg->getMaxOpenTickets() > 0 && strcasecmp($origin, 'staff') && ($client = Client::lookupByEmail($vars['email'])) && ($openTickets = $client->getNumOpenTickets()) && $openTickets >= $cfg->getMaxOpenTickets()) { $errors['err'] = "You've reached the maximum open tickets allowed."; $ost->logWarning('Ticket denied -' . $vars['email'], sprintf('Max open tickets (%d) reached for %s ', $cfg->getMaxOpenTickets(), $vars['email'])); return 0; } } //Init ticket filters... $ticket_filter = new TicketFilter($origin, $vars); // Make sure email contents should not be rejected if ($ticket_filter && ($filter = $ticket_filter->shouldReject())) { $errors['err'] = 'Ticket denied. Error #403'; $errors['errno'] = 403; $ost->logWarning('Ticket denied', sprintf('Ticket rejected ( %s) by filter "%s"', $vars['email'], $filter->getName())); 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 due date'; } elseif (strtotime($vars['duedate'] . ' ' . $vars['time']) <= time()) { $errors['duedate'] = 'Due date must be in the future'; } } //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. $priorityId = $vars['priorityId']; $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(); $priorityId = $priorityId ? $priorityId : $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(); $priorityId = $priorityId ? $priorityId : $email->getPriorityId(); if ($autorespond) { $autorespond = $email->autoRespond(); } $email = null; $source = 'Email'; } //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 ------------------------ */ 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] } $dept = $ticket->getDept(); //post the message. unset($vars['cannedattachments']); //Ticket::open() might have it set as part of open & respond. $vars['title'] = $vars['subject']; //Use the initial subject as title of the post. $message = $ticket->postMessage($vars, $origin, false); // 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 ************/ //Override auto responder if the FROM email is one of the internal emails...loop control. if ($autorespond && Email::getIdByEmail($ticket->getEmail())) { $autorespond = false; } # Messages that are clearly auto-responses from email systems should # not have a return 'ping' message if ($autorespond && $message && $message->isAutoResponse()) { $autorespond = false; } //Don't auto respond to mailer daemons. if ($autorespond && (strpos(strtolower($vars['email']), 'mailer-daemon@') !== false || strpos(strtolower($vars['email']), 'postmaster@') !== false)) { $autorespond = false; } //post canned auto-response IF any (disables new ticket auto-response). if ($vars['cannedResponseId'] && $ticket->postCannedReply($vars['cannedResponseId'], $message->getId(), $autorespond)) { $ticket->markUnAnswered(); //Leave the ticket as unanswred. $autorespond = false; } //Check department's auto response settings // XXX: Dept. setting doesn't affect canned responses. if ($autorespond && $dept && !$dept->autoRespONNewTicket()) { $autorespond = false; } /***** See if we need to send some alerts ****/ $ticket->onNewTicket($message, $autorespond, $alertstaff); /************ check if the user JUST reached the max. open tickets limit **********/ if ($cfg->getMaxOpenTickets() > 0 && ($client = $ticket->getClient()) && $client->getNumOpenTickets() == $cfg->getMaxOpenTickets()) { $ticket->onOpenLimit($autorespond && strcasecmp($origin, 'staff')); } /* Start tracking ticket lifecycle events */ $ticket->logEvent('created'); /* Phew! ... time for tea (KETEPA) */ return $ticket; }