public static function import_email($ticket_account_id, $import = true, $debug = false) { require_once 'includes/plugin_ticket/cron/rfc822_addresses.php'; require_once 'includes/plugin_ticket/cron/mime_parser.php'; $admins_rel = self::get_ticket_staff_rel(); $created_tickets = array(); $ticket_account_id = (int) $ticket_account_id; $account = self::get_ticket_account($ticket_account_id); if (!$account) { return false; } $email_account_address = $account['email']; $email_username = $account['username']; $email_password = $account['password']; $email_host = $account['host']; $email_port = $account['port']; $reply_from_user_id = $account['default_user_id']; $support_type = (int) $account['default_type']; $subject_regex = $account['subject_regex']; $body_regex = $account['body_regex']; $to_regex = $account['to_regex']; $search_string = $account['search_string']; $mailbox = $account['mailbox']; $imap = (int) $account['imap']; $secure = (int) $account['secure']; $start_date = $account['start_date'] && $account['start_date'] != '0000-00-00' ? $account['start_date'] : false; if (!$email_host || !$email_username) { return false; } // try to connect with ssl first: $ssl = $secure ? '/ssl' : ''; if ($imap) { $host = '{' . $email_host . ':' . $email_port . '/imap' . $ssl . '/novalidate-cert}' . $mailbox; if ($debug) { echo "Connecting to {$host} <br>\n"; } $mbox = imap_open($host, $email_username, $email_password); } else { $host = '{' . $email_host . ':' . $email_port . '/pop3' . $ssl . '/novalidate-cert}' . $mailbox; if ($debug) { echo "Connecting to {$host} <br>\n"; } $mbox = imap_open($host, $email_username, $email_password); } if (!$mbox) { // todo: send email letting them know bounce checking failed? echo 'Failed to connect when checking for support ticket emails.' . imap_last_error(); imap_errors(); return false; } update_insert('ticket_account_id', $account['ticket_account_id'], 'ticket_account', array('last_checked' => time())); $MC = imap_check($mbox); //echo 'Connected'.$MC->Nmsgs; // do a search if $search_results = array(-1); if ($imap && $search_string) { //imap_sort($mbox,SORTARRIVAL,0); // we do a hack to support multiple searches in the imap string. if (strpos($search_string, '||')) { $search_strings = explode('||', $search_string); } else { $search_strings = array($search_string); } $search_results = array(); foreach ($search_strings as $this_search_string) { $this_search_string = trim($this_search_string); if (!$this_search_string) { return false; } if ($debug) { echo "Searching for {$this_search_string} <br>\n"; } $this_search_results = imap_search($mbox, $this_search_string); if ($debug) { echo " -- found " . count($this_search_results) . " results <br>\n"; } $search_results = array_merge($search_results, $this_search_results); } if (!$search_results) { echo "No search results for {$search_string} "; return false; } else { sort($search_results); } } imap_errors(); //print_r($search_results);//imap_close($mbox);return false; $sorted_emails = array(); foreach ($search_results as $search_result) { if ($search_result >= 0) { $result = imap_fetch_overview($mbox, $search_result, 0); } else { //$result = imap_fetch_overview($mbox,"1:100",0); $result = imap_fetch_overview($mbox, "1:" . min(100, $MC->Nmsgs), 0); } foreach ($result as $overview) { if (!isset($overview->subject) && (!isset($overview->date) || !$overview->date)) { continue; } $overview->subject = self::_subject_decode(isset($overview->subject) ? (string) $overview->subject : ''); if ($subject_regex && !preg_match($subject_regex, $overview->subject)) { continue; } if (!isset($overview->date)) { $overview->date = date('Y-m-d H:i:s'); } if ($start_date > 1000) { if (strtotime($overview->date) < strtotime($start_date)) { continue; } } $message_id = isset($overview->message_id) ? (string) $overview->message_id : false; if (!$message_id) { $overview->message_id = $message_id = md5($overview->subject . $overview->date); } //echo "#{$overview->msgno} ({$overview->date}) - From: {$overview->from} <br> {$this_subject} <br>\n"; // check this email hasn't been processed before. // check this message hasn't been processed yet. $ticket = get_single('ticket_message', 'message_id', $message_id); if ($ticket) { continue; } // get ready to sort them. $overview->time = strtotime($overview->date); $sorted_emails[] = $overview; } } if (!function_exists('dtbaker_ticket_import_sort')) { function dtbaker_ticket_import_sort($a, $b) { return $a->time > $b->time; } } uasort($sorted_emails, 'dtbaker_ticket_import_sort'); $message_number = 0; foreach ($sorted_emails as $overview) { $message_number++; $message_id = (string) $overview->message_id; if ($debug) { ?> <div style="padding:5px; border:1px solid #EFEFEF; margin:4px;"> <div> <strong><?php echo $message_number; ?> </strong> Date: <strong><?php echo $overview->date; ?> </strong> <br/> Subject: <strong><?php echo htmlspecialchars($overview->subject); ?> </strong> <br/> From: <strong><?php echo htmlspecialchars($overview->from); ?> </strong> To: <strong><?php echo htmlspecialchars($overview->to); ?> </strong> <!-- <a href="#" onclick="document.getElementById('msg_<?php echo $message_number; ?> ').style.display='block'; return false;">view body</a> </div> <div style="display:none; padding:10px; border:1px solid #CCC;" id="msg_<?php echo $message_number; ?> "> <?php // echo htmlspecialchars($results['Data']); ?> --> </div> </div> <?php } if (!$import) { continue; } $tmp_file = tempnam(_UCM_FOLDER . '/temp/', 'ticket'); imap_savebody($mbox, $tmp_file, $overview->msgno); $mail_content = file_get_contents($tmp_file); $mime = new mime_parser_class(); $mime->mbox = 0; $mime->decode_bodies = 1; $mime->ignore_syntax_errors = 1; $parameters = array('Data' => $mail_content); $parse_success = false; if (!$mime->Decode($parameters, $decoded)) { //echo 'MIME message decoding error: '.$mime->error.' at position '.$mime->error_position."\n"; // TODO - send warning email to admin. send_error("Failed to decode this email: " . $mail_content); $parse_success = true; // so it delets the email below if that account setting is setfalse; } else { for ($message = 0; $message < count($decoded); $message++) { if ($mime->Analyze($decoded[$message], $results)) { if (isset($results['From'][0]['address'])) { $from_address = $results['From'][0]['address']; } else { continue; } /*$results: Array ( [Type] => html [Description] => HTML message [Encoding] => iso-8859-1 [Data] => asdfasdf [Alternative] => Array ( [0] => Array ( [Type] => text [Description] => Text message [Encoding] => iso-8859-1 [Data] => asdfasdf ) ) [Subject] => [TICKET:004372] Re: Testing cc and bcc fields... [Date] => Sun, 24 Mar 2013 22:04:49 +1000 [From] => Array ( [0] => Array ( [address] => email@gmail.com [name] => Dave ) ) [To] => Array ( [0] => Array ( [address] => email@dtbaker. [name] => dtbaker Support ) [1] => Array ( [address] => email+test@gmail.com ) ) [Cc] => Array ( [0] => Array ( [address] => email+testcc@gmail.com ) [1] => Array ( [address] => info@email.com.au [name] => Hayley ) ) ) */ if ($to_regex) { $to_match = false; foreach ($results['To'] as $possible_to_address) { if (preg_match($to_regex, $possible_to_address['address'])) { $to_match = true; } } if (!$to_match) { continue; } } // find out which accout this sender is from. if (preg_match('/@(.*)$/', $from_address, $matches)) { // run a hook now to parse the from address. $domain = $matches[1]; // find this sender in the database. // if we cant find this sender/customer in the database // then we add this customer as a "support user" to the default customer for this ticketing system. // based on the "to" address of this message. //store this as an eamil $email_to = ''; $email_to_first = current($results['To']); if ($email_to_first) { $email_to = $email_to_first['address']; } // work out the from and to users. $from_user_id = 0; $to_user_id = 0; // this is admin. leave blank for now i guess. // try to find a user based on this from email address. $sql = "SELECT * FROM `" . _DB_PREFIX . "user` u WHERE u.`email` LIKE '" . mysql_real_escape_string($from_address) . "' ORDER BY `date_created` DESC"; $ticket_user_account = $from_user = qa1($sql); // convert the name if it's encoded strangely: if (isset($results['From'][0]['name']) && strlen($results['From'][0]['name']) && isset($results['Encoding']) && strtolower($results['Encoding']) != 'utf8' && strtolower($results['Encoding']) != 'utf-8') { //$name_decoded = quoted_printable_decode($results['From'][0]['name']); if (function_exists('mb_convert_encoding')) { $name_decoded = mb_convert_encoding($results['From'][0]['name'], 'UTF-8', $results['Encoding']); if (strlen($name_decoded) > 0) { $results['From'][0]['name'] = $name_decoded; } } } // todo! this user may be in the system twice! // eg: once from submitting a ticket - then again when creating that user as a contact under a different customer. // so we find the latest entry and use that... ^^ done! updated the above to sort by date updated. if ($from_user) { $from_user_id = $from_user['user_id']; // woo!!found a user. assign this customer to the ticket. if ($from_user['customer_id']) { //$account['default_customer_id'] = $from_user['customer_id']; } } else { if (module_config::c('ticket_allow_new_from_email', 1)) { // create a user under this account customer because we allow new emails to be created if ($account['default_customer_id']) { // create a new support user! go go! $ticket_user_account = $from_user = array('name' => isset($results['From'][0]['name']) ? $results['From'][0]['name'] : $from_address, 'customer_id' => $account['default_customer_id'], 'email' => $from_address, 'status_id' => 1, 'password' => substr(md5(time() . mt_rand(0, 600)), 3)); global $plugins; $from_user_id = $plugins['user']->create_user($from_user, 'support'); $ticket_user_account['user_id'] = $from_user_id; } else { $ticket_user_account = $from_user = array('name' => isset($results['From'][0]['name']) ? $results['From'][0]['name'] : $from_address, 'customer_id' => -1, 'email' => $from_address, 'status_id' => 1, 'password' => substr(md5(time() . mt_rand(0, 600)), 3)); global $plugins; $from_user_id = $plugins['user']->create_user($from_user, 'support'); $ticket_user_account['user_id'] = $from_user_id; //echo 'Failed - no from account set'; //continue; } } } if (!$from_user_id) { // creating a new user for this ticket. not allowed for spam reasons sometimes. if (module_config::c('ticket_allow_new_from_email', 1)) { // failed to create a user in the database. echo 'Failed - cannot find the from user id'; echo $from_address . ' to ' . var_export($results['To'], true) . ' : subject: ' . $overview->subject . '<hr>'; continue; } else { // new option to ignore these emails and force people to submit new tickets via the web interface // send an autoreply to this user saying that their ticket was not created. $temp_from_user = array('name' => isset($results['From'][0]['name']) ? $results['From'][0]['name'] : $from_address, 'email' => $from_address); module_ticket::send_customer_rejection_alert($temp_from_user, $overview->subject); echo 'Rejecting new tickets'; $parse_success = true; continue; } } $message_type_id = _TICKET_MESSAGE_TYPE_CREATOR; // from an end user. if (strtolower($from_address) == strtolower($email_account_address)) { $message_type_id = _TICKET_MESSAGE_TYPE_ADMIN; // from an admin replying via email. } else { if (strtolower($from_address) == strtolower(module_config::c('ticket_admin_email_alert'))) { $message_type_id = _TICKET_MESSAGE_TYPE_ADMIN; // from an admin replying via email. } else { if (isset($admins_rel[$from_user_id])) { $message_type_id = _TICKET_MESSAGE_TYPE_ADMIN; // from an admin replying via email. } } } $sql = "SELECT * FROM `" . _DB_PREFIX . "user` u WHERE u.`email` LIKE '" . mysql_real_escape_string($email_to) . "'"; $to_user_temp = qa1($sql); if ($to_user_temp) { $to_user_id = $to_user_temp['user_id']; // hack for BCC support (eg: email invoice, bcc goes to our ticket email address). if ($message_type_id == _TICKET_MESSAGE_TYPE_ADMIN) { // swap these around. the email is coming from us to the customer. $ticket_user_account = array('customer_id' => $to_user_temp['customer_id'], 'user_id' => $to_user_temp['user_id']); } } $ticket_id = false; $new_message = true; // check if the subject matches an existing ticket subject. if (preg_match('#\\[TICKET:(\\d+)\\]#i', $overview->subject, $subject_matches) || preg_match('#\\#(\\d+)#', $overview->subject, $subject_matches)) { // found an existing ticket. // find this ticket in the system. $ticket_id = ltrim($subject_matches[1], '0'); // see if it exists. $existing_ticket = get_single('ticket', 'ticket_id', $ticket_id); if ($existing_ticket) { // woot! // search to see if this "from" address is in any of the past ticket messages. $valid_previous_contact = false; if ($message_type_id == _TICKET_MESSAGE_TYPE_ADMIN) { $valid_previous_contact = true; } else { $past_ticket_messages = self::get_ticket_messages($existing_ticket['ticket_id'], true); //foreach($past_ticket_messages as $past_ticket_message){ while ($past_ticket_message = mysql_fetch_assoc($past_ticket_messages)) { $past_header_cache = @unserialize($past_ticket_message['cache']); $past_to_temp = array(); if ($past_ticket_message['to_user_id']) { $past_to_temp = module_user::get_user($past_ticket_message['to_user_id'], false); } else { if ($past_header_cache && isset($past_header_cache['to_email'])) { $past_to_temp['email'] = $past_header_cache['to_email']; } } if (isset($past_to_temp['email']) && strtolower($past_to_temp['email']) == strtolower($from_address)) { $valid_previous_contact = true; break; } foreach (array('to_emails', 'cc_emails', 'bcc_emails') as $header_cache_key) { if ($past_header_cache && isset($past_header_cache[$header_cache_key]) && is_array($past_header_cache[$header_cache_key])) { foreach ($past_header_cache[$header_cache_key] as $to_email_additional) { if (isset($to_email_additional['address']) && strlen($to_email_additional['address']) && strtolower($to_email_additional['address']) == strtolower($from_address)) { $valid_previous_contact = true; break 3; } } } } } } if ($valid_previous_contact) { update_insert('ticket_id', $ticket_id, 'ticket', array('status_id' => _TICKET_STATUS_IN_PROGRESS_ID, 'last_message_timestamp' => strtotime($overview->date))); $new_message = false; } else { // create new message based on this one. // remove the old ticket ID number from subject $ticket_id = false; $overview->subject = str_replace($subject_matches[0], '', $overview->subject); } } else { // fail.. $ticket_id = false; } } else { // we search for this subject, and this sender, to see if they have sent a follow up // before we started the ticketing system. // handy for importing an existing inbox with replies etc.. // check to see if the subject matches any existing subjects. $search_subject1 = trim(preg_replace('#^Re:?\\s*#i', '', $overview->subject)); $search_subject2 = trim(preg_replace('#^Fwd?:?\\s*#i', '', $overview->subject)); $search_subject3 = trim($overview->subject); // find any threads that match this subject, from this user id. $sql = "SELECT * FROM `" . _DB_PREFIX . "ticket` t "; $sql .= " WHERE t.`user_id` = " . (int) $from_user_id . " "; $sql .= " AND ( t.`subject` LIKE '%" . mysql_real_escape_string($search_subject1) . "%' OR "; $sql .= " t.`subject` LIKE '%" . mysql_real_escape_string($search_subject2) . "%' OR "; $sql .= " t.`subject` LIKE '%" . mysql_real_escape_string($search_subject3) . "%') "; $sql .= " ORDER BY ticket_id DESC;"; $match = qa1($sql); if (count($match) && (int) $match['ticket_id'] > 0) { // found a matching email. stoked! // add it in as a reply from the end user. $ticket_id = $match['ticket_id']; update_insert('ticket_id', $ticket_id, 'ticket', array('status_id' => _TICKET_STATUS_IN_PROGRESS_ID, 'last_message_timestamp' => strtotime($overview->date))); $new_message = false; } if (!$ticket_id) { // now we see if any match the "TO" address, ie: it's us replying to the user. // handly from a gmail import. if ($email_to) { $sql = "SELECT * FROM `" . _DB_PREFIX . "user` u WHERE u.`email` LIKE '" . mysql_real_escape_string($email_to) . "'"; $temp_to_user = qa1($sql); if ($temp_to_user && $temp_to_user['user_id']) { // we have sent emails to this user before... // check to see if the subject matches any existing subjects. $sql = "SELECT * FROM `" . _DB_PREFIX . "ticket` t "; $sql .= " WHERE t.`user_id` = " . (int) $temp_to_user['user_id'] . " "; $sql .= " AND ( t.`subject` LIKE '%" . mysql_real_escape_string($search_subject1) . "%' OR "; $sql .= " t.`subject` LIKE '%" . mysql_real_escape_string($search_subject2) . "%' OR "; $sql .= " t.`subject` LIKE '%" . mysql_real_escape_string($search_subject3) . "%') "; $sql .= " ORDER BY ticket_id DESC;"; $match = qa1($sql); if (count($match) && (int) $match['ticket_id'] > 0) { // found a matching email. stoked! // add it in as a reply from the end user. $ticket_id = $match['ticket_id']; update_insert('ticket_id', $ticket_id, 'ticket', array('status_id' => _TICKET_STATUS_IN_PROGRESS_ID, 'last_message_timestamp' => strtotime($overview->date))); $new_message = false; } } } } } if (!$ticket_id) { $ticket_id = update_insert('ticket_id', 'new', 'ticket', array('subject' => $overview->subject, 'ticket_account_id' => $account['ticket_account_id'], 'status_id' => _TICKET_STATUS_NEW_ID, 'user_id' => $ticket_user_account['user_id'], 'customer_id' => $ticket_user_account['customer_id'], 'assigned_user_id' => $reply_from_user_id, 'ticket_type_id' => $support_type, 'last_message_timestamp' => strtotime($overview->date))); } if (!$ticket_id) { echo 'Error creating ticket'; continue; } module_ticket::mark_as_unread($ticket_id); $cache = array('from_email' => $from_address, 'to_email' => $email_to, 'to_emails' => isset($results['To']) && is_array($results['To']) ? $results['To'] : array(), 'cc_emails' => isset($results['Cc']) && is_array($results['Cc']) ? $results['Cc'] : array()); // pull otu the email bodyu. $body = $results['Data']; //if($from_address=='*****@*****.**'){ if (isset($results['Encoding']) && strtolower($results['Encoding']) != 'utf8' && strtolower($results['Encoding']) != 'utf-8') { //mail('*****@*****.**','Ticket import results: Encoding',$results['Encoding']."\n\n".var_export($results,true)); //$body2 = quoted_printable_decode($body); if (function_exists('mb_convert_encoding')) { $body3 = mb_convert_encoding($body, 'UTF-8', $results['Encoding']); //$body3 = mb_convert_encoding($body,'HTML-ENTITIES',$results['Encoding']); //$body4 = iconv_mime_decode($body,ICONV_MIME_DECODE_CONTINUE_ON_ERROR,"UTF-8"); //mail('*****@*****.**','Ticket import results: Converted',$body . "\n\n\n\n\n ------------ " . $body2 . "\n\n\n\n\n ------------ " . $body3); if (strlen($body3) > 0) { $body = $body3; } } } //} // debug if ($results['Type'] == "html") { $is_html = true; } else { // convert body to html, so we can do wrap. $body = nl2br($body); $is_html = true; } // find the alt body. $altbody = ''; if (isset($results['Alternative']) && is_array($results['Alternative'])) { foreach ($results['Alternative'] as $alt_id => $alt) { if ($alt['Type'] == "text") { $altbody = $alt['Data']; // if($from_address=='*****@*****.**'){ if (isset($results['Encoding']) && strtolower($results['Encoding']) != 'utf8' && strtolower($results['Encoding']) != 'utf-8') { //$altbody2 = quoted_printable_decode($altbody); if (function_exists('mb_convert_encoding')) { $altbody3 = mb_convert_encoding($altbody, 'UTF-8', $results['Encoding']); if (strlen($altbody3) > 0) { $altbody = $altbody3; } } } //} break; } } } if (!$altbody) { // should really never happen, but who knows. // edit - i think this happens with godaddy webmailer. $altbody = $body; // todo: strip any html. $altbody = preg_replace('#<br[^>]*>\\n*#imsU', "\n", $altbody); $altbody = strip_tags($altbody); } // pass the body and altbody through a hook so we can modify it if needed. // eg: for envato tickets we strip the header/footer out and check the link to see if the buyer really bought anything. // run_hook(... //echo "<hr>$body<hr>$altbody<hr><br><br><br>"; // save the message! $ticket_message_id = update_insert('ticket_message_id', 'new', 'ticket_message', array('ticket_id' => $ticket_id, 'message_id' => $message_id, 'content' => $altbody, 'htmlcontent' => $body, 'message_time' => strtotime($overview->date), 'message_type_id' => $message_type_id, 'from_user_id' => $from_user_id, 'to_user_id' => $to_user_id, 'cache' => serialize($cache), 'status_id' => _TICKET_STATUS_IN_PROGRESS_ID)); if (isset($results['Related'])) { foreach ($results['Related'] as $related) { if (isset($related['FileName']) && $related['FileName']) { // save as attachment against this email. $attachment_id = update_insert('ticket_message_attachment_id', 'new', 'ticket_message_attachment', array('ticket_id' => $ticket_id, 'ticket_message_id' => $ticket_message_id, 'file_name' => $related['FileName'], 'content_type' => $related['Type'] . (isset($related['SubType']) ? '/' . $related['SubType'] : ''))); $result = file_put_contents('includes/plugin_ticket/attachments/' . $attachment_id . '', $related['Data']); if (!$result) { send_error("Failed to save attachment (named: " . $related['FileName'] . " for this email: \n\n\n\n" . var_export($related, true) . "\n\n\n" . var_export($results, true) . "\n\n\n" . $mail_content); } } } } if (isset($results['Attachments'])) { foreach ($results['Attachments'] as $related) { if (isset($related['FileName']) && $related['FileName']) { // save as attachment against this email. $attachment_id = update_insert('ticket_message_attachment_id', 'new', 'ticket_message_attachment', array('ticket_id' => $ticket_id, 'ticket_message_id' => $ticket_message_id, 'file_name' => $related['FileName'], 'content_type' => $related['Type'] . (isset($related['SubType']) ? '/' . $related['SubType'] : ''))); $result = file_put_contents('includes/plugin_ticket/attachments/' . $attachment_id . '', $related['Data']); if (!$result) { send_error("Failed to save attachment (named: " . $related['FileName'] . " for this email: \n\n\n\n" . var_export($related, true) . "\n\n\n" . var_export($results, true) . "\n\n\n" . $mail_content); } } } } //$new_message && if (!preg_match('#failure notice#i', $overview->subject)) { // we don't sent ticket autoresponders when the from user and to user are teh same if ($from_user_id && $to_user_id && $from_user_id == $to_user_id) { } else { $created_tickets[$ticket_id] = $ticket_id; } } $parse_success = true; } } } } if ($parse_success && $account['delete']) { // remove email from inbox if needed. imap_delete($mbox, $overview->msgno); } unlink($tmp_file); } imap_errors(); //} imap_expunge($mbox); imap_close($mbox); imap_errors(); return $created_tickets; }