function doSplitMessageAction() { @($id = DevblocksPlatform::importGPC($_REQUEST['id'], 'integer', 0)); if (null == ($orig_message = DAO_Ticket::getMessage($id))) { return; } if (null == ($orig_headers = $orig_message->getHeaders())) { return; } if (null == ($orig_ticket = DAO_Ticket::getTicket($orig_message->ticket_id))) { return; } if (null == ($messages = DAO_Ticket::getMessagesByTicket($orig_message->ticket_id))) { return; } // Create a new ticket $new_ticket_mask = CerberusApplication::generateTicketMask(); $new_ticket_id = DAO_Ticket::createTicket(array(DAO_Ticket::CREATED_DATE => $orig_message->created_date, DAO_Ticket::UPDATED_DATE => $orig_message->created_date, DAO_Ticket::CATEGORY_ID => $orig_ticket->category_id, DAO_Ticket::FIRST_MESSAGE_ID => $orig_message->id, DAO_Ticket::FIRST_WROTE_ID => $orig_message->address_id, DAO_Ticket::LAST_WROTE_ID => $orig_message->address_id, DAO_Ticket::LAST_ACTION_CODE => CerberusTicketActionCode::TICKET_OPENED, DAO_Ticket::IS_CLOSED => CerberusTicketStatus::OPEN, DAO_Ticket::IS_DELETED => 0, DAO_Ticket::MASK => $new_ticket_mask, DAO_Ticket::SUBJECT => isset($orig_headers['subject']) ? $orig_headers['subject'] : $orig_ticket->subject, DAO_Ticket::TEAM_ID => $orig_ticket->team_id)); // [TODO] SLA? // Copy all the original tickets requesters $orig_requesters = DAO_Ticket::getRequestersByTicket($orig_ticket->id); foreach ($orig_requesters as $orig_req_id => $orig_req_addy) { DAO_Ticket::createRequester($orig_req_id, $new_ticket_id); } // Pull the message off the ticket (reparent) unset($messages[$orig_message->id]); DAO_Message::update($orig_message->id, array(DAO_Message::TICKET_ID => $new_ticket_id)); //[mdf] [CHD-979] The ticket id is also in the message_header table, so update those too $message_headers = DAO_MessageHeader::getAll($orig_message->id); foreach ($message_headers as $hk => $hv) { DAO_MessageHeader::create($orig_message->id, $new_ticket_id, $hk, $hv); } // Reindex the original ticket (last wrote, etc.) $last_message = end($messages); /* @var CerberusMessage $last_message */ DAO_Ticket::updateTicket($orig_ticket->id, array(DAO_Ticket::LAST_WROTE_ID => $last_message->address_id)); // Remove requester if they don't still have messages on the original ticket reset($messages); $found = false; if (is_array($messages)) { foreach ($messages as $msgid => $msg) { if ($msg->address_id == $orig_message->address_id) { $found = true; break; } } } if (!$found) { DAO_Ticket::deleteRequester($orig_ticket->id, $orig_message->address_id); } DevblocksPlatform::redirect(new DevblocksHttpResponse(array('display', $new_ticket_mask))); }
private static function _markTicketAs($ticket_id, $spam = true) { // [TODO] Make sure we can't retrain tickets which are already spam trained // [TODO] This is a performance killer $ticket = DAO_Ticket::getTicket($ticket_id); if ($ticket->spam_training != CerberusTicketSpamTraining::BLANK) { return TRUE; } // pull up text of first ticket message // [TODO] This is a performance killer $first_message = DAO_Ticket::getMessage($ticket->first_message_id); if (empty($first_message)) { return FALSE; } // [TODO] This is a performance killer $headers = DAO_MessageHeader::getAll($first_message->id); // Pass text to analyze() to get back interesting words $content = ''; if (!empty($ticket->subject)) { // SplitCamelCapsSubjects $hits = preg_split("{(?<=[a-z])(?=[A-Z])}x", $ticket->subject); if (is_array($hits) && !empty($hits)) { $content .= implode(' ', $hits); } } $content .= ' ' . $first_message->getContent(); if (strlen($content) > self::MAX_BODY_LENGTH) { $content = substr($content, 0, strrpos(substr($content, 0, self::MAX_BODY_LENGTH), ' ')); } $words = self::processText($content); // Train interesting words as spam/notspam // $out = self::_calculateSpamProbability($words); // self::_trainWords($out['words'],$spam); self::_trainWords($words, $spam); // [TODO] Testing, train all words // Increase the bayes_stats spam or notspam total count by 1 // [TODO] This is a performance killer (could be done in batches) if ($spam) { DAO_Bayes::addOneToSpamTotal(); DAO_Address::addOneToSpamTotal($ticket->first_wrote_address_id); } else { DAO_Bayes::addOneToNonSpamTotal(); DAO_Address::addOneToNonSpamTotal($ticket->first_wrote_address_id); } // Forced training should leave a cache of 0.0001 or 0.9999 on the ticket table $fields = array('spam_score' => $spam ? 0.9999 : 0.0001, 'spam_training' => $spam ? CerberusTicketSpamTraining::SPAM : CerberusTicketSpamTraining::NOT_SPAM); DAO_Ticket::updateTicket($ticket_id, $fields); return TRUE; }
function getHeaders() { return DAO_MessageHeader::getAll($this->id); }
private function _getMessageXML($id) { $message = DAO_Ticket::getMessage($id); /* @var $message CerberusMessage */ if (is_null($message)) { $this->_error("ID {$id} not valid."); } $message_content = DAO_MessageContent::get($id); $message_headers = DAO_MessageHeader::getAll($id); $message_notes = DAO_MessageNote::getByMessageId($id); $xml_out = new SimpleXMLElement("<message></message>"); $xml_out->addChild('id', $message->id); $xml_out->addChild('ticket_id', $message->ticket_id); $xml_out->addChild('created_date', $message->created_date); $xml_out->addChild('address_id', $message->address_id); $xml_out->addChild('is_outgoing', $message->is_outgoing); $xml_out->addChild('worker_id', $message->worker_id); $xml_out->addChild('content', $message_content); $headers = $xml_out->addChild('headers'); foreach ($message_headers as $header_name => $header_value) { $headers->addChild($header_name, $header_value); } $xml_notes = $xml_out->addChild('notes'); foreach ($message_notes as $note) { $xml_note = $xml_notes->addChild('note'); $xml_note->addChild('id', $note->id); $xml_note->addChild('type', $note->type); $xml_note->addChild('message_id', $note->message_id); $xml_note->addChild('created', $note->created); $xml_note->addChild('worker_id', $note->worker_id); $xml_note->addChild('content', $note->content); } return $xml_out; }
static function sendTicketMessage($properties = array()) { $settings = DevblocksPlatform::getPluginSettingsService(); $helpdesk_senders = CerberusApplication::getHelpdeskSenders(); @($from_addy = $settings->get('cerberusweb.core', CerberusSettings::DEFAULT_REPLY_FROM, $_SERVER['SERVER_ADMIN'])); @($from_personal = $settings->get('cerberusweb.core', CerberusSettings::DEFAULT_REPLY_PERSONAL, '')); // [TODO] If we still don't have a $from_addy we need a graceful failure. /* * [TODO] Move these into constants? 'message_id' -----'ticket_id' 'subject' 'to' 'cc' 'bcc' 'content' 'files' 'closed' 'ticket_reopen' 'unlock_date' 'bucket_id' 'agent_id', 'is_autoreply', 'dont_send', 'dont_save_copy' */ $mail_succeeded = true; try { // objects $mail_service = DevblocksPlatform::getMailService(); $mailer = $mail_service->getMailer(CerberusMail::getMailerDefaults()); $mail = $mail_service->createMessage(); // properties @($reply_message_id = $properties['message_id']); @($content = $properties['content']); @($files = $properties['files']); @($forward_files = $properties['forward_files']); @($worker_id = $properties['agent_id']); @($subject = $properties['subject']); $message = DAO_Ticket::getMessage($reply_message_id); $message_headers = DAO_MessageHeader::getAll($reply_message_id); $ticket_id = $message->ticket_id; $ticket = DAO_Ticket::getTicket($ticket_id); // [TODO] Check that message|ticket isn't NULL // If this ticket isn't spam trained and our outgoing message isn't an autoreply if ($ticket->spam_training == CerberusTicketSpamTraining::BLANK && (!isset($properties['is_autoreply']) || !$properties['is_autoreply'])) { CerberusBayes::markTicketAsNotSpam($ticket_id); } // Allow teams to override the default from/personal @($group_reply = DAO_GroupSettings::get($ticket->team_id, DAO_GroupSettings::SETTING_REPLY_FROM, '')); @($group_personal = DAO_GroupSettings::get($ticket->team_id, DAO_GroupSettings::SETTING_REPLY_PERSONAL, '')); @($group_personal_with_worker = DAO_GroupSettings::get($ticket->team_id, DAO_GroupSettings::SETTING_REPLY_PERSONAL_WITH_WORKER, 0)); if (!empty($group_reply)) { $from_addy = $group_reply; } if (!empty($group_personal)) { $from_personal = $group_personal; } // Prefix the worker name on the personal line? if (!empty($group_personal_with_worker) && null != ($reply_worker = DAO_Worker::getAgent($worker_id))) { $from_personal = $reply_worker->getName() . (!empty($from_personal) ? ', ' . $from_personal : ""); } // Headers $mail->setFrom(array($from_addy => $from_personal)); $mail->generateId(); $headers = $mail->getHeaders(); $headers->addTextHeader('X-Mailer', 'Cerberus Helpdesk (Build ' . APP_BUILD . ')'); // Subject if (empty($subject)) { $subject = $ticket->subject; } if (!empty($properties['to'])) { // forward $mail->setSubject($subject); } else { // reply @($group_has_subject = intval(DAO_GroupSettings::get($ticket->team_id, DAO_GroupSettings::SETTING_SUBJECT_HAS_MASK, 0))); @($group_subject_prefix = DAO_GroupSettings::get($ticket->team_id, DAO_GroupSettings::SETTING_SUBJECT_PREFIX, '')); $prefix = sprintf("[%s#%s] ", !empty($group_subject_prefix) ? $group_subject_prefix . ' ' : '', $ticket->mask); $mail->setSubject(sprintf('Re: %s%s', $group_has_subject ? $prefix : '', $subject)); } // References if (!empty($message) && false !== @($in_reply_to = $message_headers['message-id'])) { $headers->addTextHeader('References', $in_reply_to); $headers->addTextHeader('In-Reply-To', $in_reply_to); } // Auto-reply handling (RFC-3834 compliant) if (isset($properties['is_autoreply']) && $properties['is_autoreply']) { $headers->addTextHeader('Auto-Submitted', 'auto-replied'); if (null == ($first_address = DAO_Address::get($ticket->first_wrote_address_id))) { return; } // Don't send e-mail to ourselves if (isset($helpdesk_senders[$first_address->email])) { return; } // Make sure we haven't mailed this address an autoreply within 5 minutes if ($first_address->last_autoreply > 0 && $first_address->last_autoreply > time() - 300) { return; } $first_email = strtolower($first_address->email); $first_split = explode('@', $first_email); if (!is_array($first_split) || count($first_split) != 2) { return; } // If return-path is blank if (isset($message_headers['return-path']) && $message_headers['return-path'] == '<>') { return; } // Ignore bounces if ($first_split[0] == "postmaster" || $first_split[0] == "mailer-daemon") { return; } // Ignore autoresponses to autoresponses if (isset($message_headers['auto-submitted']) && $message_headers['auto-submitted'] != 'no') { return; } if (isset($message_headers['precedence']) && ($message_headers['precedence'] == 'list' || $message_headers['precedence'] == 'junk' || ($message_headers['precedence'] = 'bulk'))) { return; } // Set the auto-reply date for this address to right now DAO_Address::update($ticket->first_wrote_address_id, array(DAO_Address::LAST_AUTOREPLY => time())); // Auto-reply just to the initial requester $mail->addTo($first_address->email); // Not an auto-reply } else { // Forwards if (!empty($properties['to'])) { $aTo = DevblocksPlatform::parseCsvString(str_replace(';', ',', $properties['to'])); if (is_array($aTo)) { foreach ($aTo as $to_addy) { $mail->addTo($to_addy); } } // Replies } else { // Recipients $requesters = DAO_Ticket::getRequestersByTicket($ticket_id); if (is_array($requesters)) { foreach ($requesters as $requester) { /* @var $requester Model_Address */ $mail->addTo($requester->email); } } } // Ccs if (!empty($properties['cc'])) { $aCc = DevblocksPlatform::parseCsvString(str_replace(';', ',', $properties['cc'])); $mail->setCc($aCc); } // Bccs if (!empty($properties['bcc'])) { $aBcc = DevblocksPlatform::parseCsvString(str_replace(';', ',', $properties['bcc'])); $mail->setBcc($aBcc); } } /* * [IMPORTANT -- Yes, this is simply a line in the sand.] * You're welcome to modify the code to meet your needs, but please respect * our licensing. Buy a legitimate copy to help support the project! * http://www.cerberusweb.com/ */ $license = CerberusLicense::getInstance(); if (empty($license) || @empty($license['serial'])) { $content .= base64_decode("DQoNCi0tLQ0KQ29tYmF0IHNwYW0gYW5kIGltcHJvdmUgcmVzc" . "G9uc2UgdGltZXMgd2l0aCBDZXJiZXJ1cyBIZWxwZGVzayA0LjAhDQpodHRwOi8vd3d3LmNlc" . "mJlcnVzd2ViLmNvbS8NCg"); } // Body $mail->setBody($content); // Mime Attachments if (is_array($files) && !empty($files)) { foreach ($files['tmp_name'] as $idx => $file) { if (empty($file) || empty($files['name'][$idx])) { continue; } $mail->attach(Swift_Attachment::fromPath($file)->setFilename($files['name'][$idx])); } } // Forward Attachments if (!empty($forward_files) && is_array($forward_files)) { $attachments_path = APP_STORAGE_PATH . '/attachments/'; foreach ($forward_files as $file_id) { $attachment = DAO_Attachment::get($file_id); $attachment_path = $attachments_path . $attachment->filepath; $mail->attach(Swift_Attachment::fromPath($attachment_path)->setFilename($attachment->display_name)); } } if (!DEMO_MODE) { // If we're not supposed to send if (isset($properties['dont_send']) && $properties['dont_send']) { // ...do nothing } else { // otherwise send if (!$mailer->send($mail)) { $mail_succeeded = false; throw new Exception('Mail not sent.'); } } } } catch (Exception $e) { // tag failure, so we can add a note to the message later $mail_succeeded = false; } // Handle post-mail actions $change_fields = array(); $fromAddressInst = CerberusApplication::hashLookupAddress($from_addy, true); $fromAddressId = $fromAddressInst->id; if ((!isset($properties['dont_keep_copy']) || !$properties['dont_keep_copy']) && (!isset($properties['is_autoreply']) || !$properties['is_autoreply'])) { $change_fields[DAO_Ticket::LAST_WROTE_ID] = $fromAddressId; $change_fields[DAO_Ticket::UPDATED_DATE] = time(); if (!empty($worker_id)) { $change_fields[DAO_Ticket::LAST_WORKER_ID] = $worker_id; $change_fields[DAO_Ticket::LAST_ACTION_CODE] = CerberusTicketActionCode::TICKET_WORKER_REPLY; } // Only change the subject if not forwarding if (!empty($subject) && empty($properties['to'])) { $change_fields[DAO_Ticket::SUBJECT] = $subject; } $fields = array(DAO_Message::TICKET_ID => $ticket_id, DAO_Message::CREATED_DATE => time(), DAO_Message::ADDRESS_ID => $fromAddressId, DAO_Message::IS_OUTGOING => 1, DAO_Message::WORKER_ID => !empty($worker_id) ? $worker_id : 0); $message_id = DAO_Message::create($fields); // Content DAO_MessageContent::create($message_id, $content); $headers = $mail->getHeaders(); // Headers foreach ($headers->getAll() as $hdr) { if (null != ($hdr_val = $hdr->getFieldBody())) { if (!empty($hdr_val)) { DAO_MessageHeader::create($message_id, $hdr->getFieldName(), CerberusParser::fixQuotePrintableString($hdr_val)); } } } // Attachments if (is_array($files) && !empty($files)) { $attachment_path = APP_STORAGE_PATH . '/attachments/'; reset($files); foreach ($files['tmp_name'] as $idx => $file) { if (empty($file) || empty($files['name'][$idx]) || !file_exists($file)) { continue; } $fields = array(DAO_Attachment::MESSAGE_ID => $message_id, DAO_Attachment::DISPLAY_NAME => $files['name'][$idx], DAO_Attachment::MIME_TYPE => $files['type'][$idx], DAO_Attachment::FILE_SIZE => filesize($file)); $file_id = DAO_Attachment::create($fields); $attachment_bucket = sprintf("%03d/", mt_rand(1, 100)); $attachment_file = $file_id; if (!file_exists($attachment_path . $attachment_bucket)) { mkdir($attachment_path . $attachment_bucket, 0775, true); } if (!is_writeable($attachment_path . $attachment_bucket)) { echo "Can't write to bucket " . $attachment_path . $attachment_bucket . "<BR>"; } copy($file, $attachment_path . $attachment_bucket . $attachment_file); @unlink($file); DAO_Attachment::update($file_id, array(DAO_Attachment::FILEPATH => $attachment_bucket . $attachment_file)); } } // add note to message if email failed if ($mail_succeeded === false) { $fields = array(DAO_MessageNote::MESSAGE_ID => $message_id, DAO_MessageNote::CREATED => time(), DAO_MessageNote::WORKER_ID => 0, DAO_MessageNote::CONTENT => 'Exception thrown while sending email: ' . $e->getMessage(), DAO_MessageNote::TYPE => Model_MessageNote::TYPE_ERROR); DAO_MessageNote::create($fields); } } // Post-Reply Change Properties if (isset($properties['closed'])) { switch ($properties['closed']) { case 0: // open $change_fields[DAO_Ticket::IS_WAITING] = 0; $change_fields[DAO_Ticket::IS_CLOSED] = 0; $change_fields[DAO_Ticket::IS_DELETED] = 0; $change_fields[DAO_Ticket::DUE_DATE] = 0; break; case 1: // closed $change_fields[DAO_Ticket::IS_WAITING] = 0; $change_fields[DAO_Ticket::IS_CLOSED] = 1; $change_fields[DAO_Ticket::IS_DELETED] = 0; if (isset($properties['ticket_reopen'])) { @($time = intval(strtotime($properties['ticket_reopen']))); $change_fields[DAO_Ticket::DUE_DATE] = $time; } break; case 2: // waiting $change_fields[DAO_Ticket::IS_WAITING] = 1; $change_fields[DAO_Ticket::IS_CLOSED] = 0; $change_fields[DAO_Ticket::IS_DELETED] = 0; if (isset($properties['ticket_reopen'])) { @($time = intval(strtotime($properties['ticket_reopen']))); $change_fields[DAO_Ticket::DUE_DATE] = $time; } break; } } // Who should handle the followup? if (isset($properties['next_worker_id'])) { $change_fields[DAO_Ticket::NEXT_WORKER_ID] = $properties['next_worker_id']; } // Allow anybody to reply after if (isset($properties['unlock_date']) && !empty($properties['unlock_date'])) { $unlock = strtotime($properties['unlock_date']); if (intval($unlock) > 0) { $change_fields[DAO_Ticket::UNLOCK_DATE] = $unlock; } } // Move if (!empty($properties['bucket_id'])) { // [TODO] Use API to move, or fire event // [TODO] Ensure team/bucket exist list($team_id, $bucket_id) = CerberusApplication::translateTeamCategoryCode($properties['bucket_id']); $change_fields[DAO_Ticket::TEAM_ID] = $team_id; $change_fields[DAO_Ticket::CATEGORY_ID] = $bucket_id; } if (!empty($ticket_id) && !empty($change_fields)) { DAO_Ticket::updateTicket($ticket_id, $change_fields); } // Outbound Reply Event (not automated reply, etc.) if (!empty($worker_id)) { $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('ticket.reply.outbound', array('ticket_id' => $ticket_id, 'worker_id' => $worker_id))); } }