function testAutoReplyHeaders()
 {
     static $headers = array('Auto-Submitted: auto-replied', 'Precedence: auto_reply', 'X-Precedence: auto_reply', 'X-Autorespond: osTicket v1.8.0.2 Released', 'Auto-Submitted: auto-generated', 'X-Autogenerated: Reply');
     foreach ($headers as $h) {
         $this->assert(TicketFilter::isAutoReply($h), $h . ": Unidentified auto-reply");
     }
 }
Example #2
0
 function isbanned($email)
 {
     return TicketFilter::isBanned($email);
 }
Example #3
0
 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;
 }
//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('&nbsp;&nbsp;<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 .= '&nbsp;&nbsp;<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();
Example #5
0
 $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());
Example #6
0
 function parse($stream)
 {
     global $cfg;
     $contents = '';
     if (is_resource($stream)) {
         while (!feof($stream)) {
             $contents .= fread($stream, 8192);
         }
     } else {
         $contents = $stream;
     }
     $parser = new Mail_Parse($contents);
     if (!$parser->decode()) {
         //Decode...returns false on decoding errors
         return $this->err('Email parse failed [' . $parser->getError() . ']');
     }
     $data = array();
     $data['emailId'] = 0;
     $data['recipients'] = array();
     $data['subject'] = $parser->getSubject();
     $data['header'] = $parser->getHeader();
     $data['mid'] = $parser->getMessageId();
     $data['priorityId'] = $parser->getPriority();
     $data['flags'] = new ArrayObject();
     //FROM address: who sent the email.
     if ($fromlist = $parser->getFromAddressList()) {
         $from = $fromlist[0];
         //Default.
         foreach ($fromlist as $fromobj) {
             if (!Validator::is_email($fromobj->mailbox . '@' . $fromobj->host)) {
                 continue;
             }
             $from = $fromobj;
             break;
         }
         $data['email'] = $from->mailbox . '@' . $from->host;
         $data['name'] = trim($from->personal, '"');
         if ($from->comment && $from->comment[0]) {
             $data['name'] .= ' (' . $from->comment[0] . ')';
         }
         //Use email address as name  when FROM address doesn't  have a name.
         if (!$data['name'] && $data['email']) {
             $data['name'] = $data['email'];
         }
     }
     /* Scan through the list of addressees (via To, Cc, and Delivered-To headers), and identify
      * how the mail arrived at the system. One of the mails should be in the system email list.
      * The recipient list (without the Delivered-To addressees) will be made available to the
      * ticket filtering system. However, addresses in the Delivered-To header should never be
      * considered for the collaborator list.
      */
     $tolist = array();
     if ($to = $parser->getToAddressList()) {
         $tolist['to'] = $to;
     }
     if ($cc = $parser->getCcAddressList()) {
         $tolist['cc'] = $cc;
     }
     if ($dt = $parser->getDeliveredToAddressList()) {
         $tolist['delivered-to'] = $dt;
     }
     foreach ($tolist as $source => $list) {
         foreach ($list as $addr) {
             if (!($emailId = Email::getIdByEmail(strtolower($addr->mailbox) . '@' . $addr->host))) {
                 //Skip virtual Delivered-To addresses
                 if ($source == 'delivered-to') {
                     continue;
                 }
                 $data['recipients'][] = array('source' => sprintf(_S("Email (%s)"), $source), 'name' => trim(@$addr->personal, '"'), 'email' => strtolower($addr->mailbox) . '@' . $addr->host);
             } elseif (!$data['emailId']) {
                 $data['emailId'] = $emailId;
             }
         }
     }
     /*
      * In the event that the mail was delivered to the system although none of the system
      * mail addresses are in the addressee lists, be careful not to include the addressee
      * in the collaborator list. Therefore, the delivered-to addressees should be flagged so they
      * are not added to the collaborator list in the ticket creation process.
      */
     if ($tolist['delivered-to']) {
         foreach ($tolist['delivered-to'] as $addr) {
             foreach ($data['recipients'] as $i => $r) {
                 if (strcasecmp($r['email'], $addr->mailbox . '@' . $addr->host) === 0) {
                     $data['recipients'][$i]['source'] = 'delivered-to';
                 }
             }
         }
     }
     //maybe we got BCC'ed??
     if (!$data['emailId']) {
         $emailId = 0;
         if ($bcc = $parser->getBccAddressList()) {
             foreach ($bcc as $addr) {
                 if ($emailId = Email::getIdByEmail($addr->mailbox . '@' . $addr->host)) {
                     break;
                 }
             }
         }
         $data['emailId'] = $emailId;
     }
     if ($parser->isBounceNotice()) {
         // Fetch the original References and assign to 'references'
         if ($headers = $parser->getOriginalMessageHeaders()) {
             $data['references'] = $headers['references'];
             $data['in-reply-to'] = @$headers['in-reply-to'] ?: null;
         }
         // Fetch deliver status report
         $data['message'] = $parser->getDeliveryStatusMessage() ?: $parser->getBody();
         $data['thread-type'] = 'N';
         $data['flags']['bounce'] = true;
     } else {
         // Typical email
         $data['message'] = $parser->getBody();
         $data['in-reply-to'] = @$parser->struct->headers['in-reply-to'];
         $data['references'] = @$parser->struct->headers['references'];
         $data['flags']['bounce'] = TicketFilter::isBounce($data['header']);
     }
     $data['to-email-id'] = $data['emailId'];
     if ($replyto = $parser->getReplyTo()) {
         $replyto = $replyto[0];
         $data['reply-to'] = $replyto->mailbox . '@' . $replyto->host;
         if ($replyto->personal) {
             $data['reply-to-name'] = trim($replyto->personal, " \t\n\r\v\"");
         }
     }
     $data['attachments'] = $parser->getAttachments();
     return $data;
 }
Example #7
0
 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;
 }
Example #8
0
 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 isBounce()
 {
     if (!isset($this->is_bounce)) {
         $this->is_bounce = $this->getEmailHeaderArray() ? TicketFilter::isBounce($this->getEmailHeaderArray()) : false;
     }
     return $this->is_bounce;
 }
Example #10
0
 function isAutoResponse()
 {
     return $this->getEmailHeader() ? TicketFilter::isAutoResponse($this->getEmailHeader()) : false;
 }
 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;
 }
Example #12
0
 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;
 }