function run() { $logger = DevblocksPlatform::getConsoleLog(); $logger->info("[Heartbeat] Starting Heartbeat Task"); // Heartbeat Event $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('cron.heartbeat', array())); }
static function update($ids, $fields) { if (!is_array($ids)) { $ids = array($ids); } /* * Make a diff for the requested objects in batches */ foreach ($ids as $id) { $objects = DAO_CustomerRecipient::get($id); $object_changes = array(); $pre_fields = get_object_vars($objects); $changes = array(); foreach ($fields as $field_key => $field_val) { // Make sure the value of the field actually changed if ($pre_fields[$field_key] != $field_val) { $changes[$field_key] = array('from' => $pre_fields[$field_key], 'to' => $field_val); } } // If we had changes if (!empty($changes)) { $object_changes[$id] = array('model' => array_merge($pre_fields, $fields), 'changes' => $changes); } /* * Make the changes */ parent::_update($ids, 'customer_recipient', $fields); } /* * Trigger an event about the changes */ if (!empty($object_changes)) { $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('dao.customer.recipient.update', array('objects' => $object_changes))); } }
private function _workerAssignedTask($event) { $translate = DevblocksPlatform::getTranslationService(); $events = DevblocksPlatform::getEventService(); $worker_id = $event->params['worker_id']; $context = $event->params['context']; $task_id = $event->params['context_id']; $mail_service = DevblocksPlatform::getMailService(); $mailer = null; // lazy load $settings = DevblocksPlatform::getPluginSettingsService(); $reply_to = $settings->get('cerberusweb.core', CerberusSettings::DEFAULT_REPLY_FROM, CerberusSettingsDefaults::DEFAULT_REPLY_FROM); $reply_personal = $settings->get('cerberusweb.core', CerberusSettings::DEFAULT_REPLY_PERSONAL, CerberusSettingsDefaults::DEFAULT_REPLY_PERSONAL); $task = DAO_Task::get($task_id); // Sanitize and combine all the destination addresses $next_worker = DAO_Worker::get($worker_id); $notify_emails = $next_worker->email; if (empty($notify_emails)) { return; } try { if (null == $mailer) { $mailer = $mail_service->getMailer(CerberusMail::getMailerDefaults()); } // Create the message $mail = $mail_service->createMessage(); $mail->setTo(array($notify_emails)); $mail->setFrom(array($reply_to => $reply_personal)); $mail->setReplyTo($reply_to); $mail->setSubject(sprintf("[Task Assignment #%d]: %s", $task->id, $task->title)); $headers = $mail->getHeaders(); $headers->addTextHeader('X-Mailer', 'Cerberus Helpdesk (Build ' . APP_BUILD . ')'); $headers->addTextHeader('Precedence', 'List'); $headers->addTextHeader('Auto-Submitted', 'auto-generated'); $body = sprintf("[Task Assignment #%d]: %s", $task->id, $task->title); $mft = DevblocksPlatform::getExtension($context, false, true); $ext = $mft->createInstance(); $url = $ext->getPermalink($task_id); $body .= "\r\n" . $url; // Comments $comments = DAO_Comment::getByContext(CerberusContexts::CONTEXT_TASK, $task_id); foreach ($comments as $comment_id => $comment) { $address = DAO_Address::get($comment->address_id); $body .= "\r\nCommented By: " . $address->first_name . " " . $address->last_name; $body .= "\r\n" . $comment->comment; } unset($comments); $body .= "\r\n"; $mail->setBody($body); $result = $mailer->send($mail); } catch (Exception $e) { echo "Task Email Notification failed to send<br>"; } }
function run() { // Heartbeat Event $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('cron.heartbeat', array())); }
function run() { $logger = DevblocksPlatform::getConsoleLog(); $logger->info("[Sensors] Starting..."); // Only pull enabled sensors with an extension_id $sensors = DAO_Sensor::getWhere(sprintf("%s=%d", DAO_Sensor::IS_DISABLED, 0)); $sensor_types = DevblocksPlatform::getExtensions('portsensor.sensor', true); if (is_array($sensors)) { foreach ($sensors as $sensor) { if (!empty($sensor->extension_id) && isset($sensor_types[$sensor->extension_id])) { // Skip external sensors if ('sensor.external' == $sensor->extension_id) { continue; } $runner = $sensor_types[$sensor->extension_id]; $output = sprintf("%s (%s)", $sensor->name, $sensor->extension_id); if (method_exists($runner, 'run')) { $fields = array(); $success = $runner->run($sensor, $fields); $fields[DAO_Sensor::UPDATED_DATE] = time(); $fields[DAO_Sensor::FAIL_COUNT] = $success ? 0 : intval($sensor->fail_count) + 1; DAO_Sensor::update($sensor->id, $fields); } $logger->info("[Sensors] Running {$output}... {$result}"); } } } // Sensor Runner Event $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('cron.sensors.post', array())); $logger->info("[Sensors] Finished!"); }
/** * Enter description here... * * @param CerberusParserMessage $message * @return integer */ public static function parseMessage(CerberusParserMessage $message, $options = array()) { /* * options: * 'no_autoreply' */ $logger = DevblocksPlatform::getConsoleLog(); $settings = DevblocksPlatform::getPluginSettingsService(); $helpdesk_senders = CerberusApplication::getHelpdeskSenders(); // Pre-parse mail filters $pre_filters = Model_PreParseRule::getMatches($message); if (is_array($pre_filters) && !empty($pre_filters)) { // Load filter action manifests for reuse $ext_action_mfts = DevblocksPlatform::getExtensions('cerberusweb.mail_filter.action', false); // Loop through all matching filters foreach ($pre_filters as $pre_filter) { // Do something with matching filter's actions foreach ($pre_filter->actions as $action_key => $action) { switch ($action_key) { case 'blackhole': return NULL; break; case 'redirect': @($to = $action['to']); CerberusMail::reflect($message, $to); return NULL; break; case 'bounce': @($msg = $action['message']); @($subject = 'Delivery failed: ' . self::fixQuotePrintableString($message->headers['subject'])); // [TODO] Follow the RFC spec on a true bounce if (null != ($fromAddressInst = CerberusParser::getAddressFromHeaders($message->headers))) { CerberusMail::quickSend($fromAddressInst->email, $subject, $msg); } return NULL; break; default: // Plugin pre-parser filter actions if (isset($ext_action_mfts[$action_key])) { if (null != @($ext_action = $ext_action_mfts[$action_key]->createInstance())) { try { /* @var $ext_action Extension_MailFilterAction */ $ext_action->run($pre_filter, $message); } catch (Exception $e) { } } } break; } } } } $headers =& $message->headers; // From if (null == ($fromAddressInst = CerberusParser::getAddressFromHeaders($headers))) { $logger->err("[Parser] 'From' address could not be created."); return NULL; } // To/Cc/Bcc $to = array(); $sTo = @$headers['to']; $bIsNew = true; if (!empty($sTo)) { // [TODO] Do we still need this RFC address parser? $to = CerberusParser::parseRfcAddress($sTo); } // Subject // Fix quote printable subject (quoted blocks can appear anywhere in subject) $sSubject = ""; if (isset($headers['subject']) && !empty($headers['subject'])) { $sSubject = $headers['subject']; if (is_array($sSubject)) { $sSubject = array_shift($sSubject); } } // The subject can still end up empty after QP decode if (empty($sSubject)) { $sSubject = "(no subject)"; } // Date $iDate = @strtotime($headers['date']); // If blank, or in the future, set to the current date if (empty($iDate) || $iDate > time()) { $iDate = time(); } // Is banned? if (1 == $fromAddressInst->is_banned) { $logger->info("[Parser] Ignoring ticket from banned address: " . $fromAddressInst->email); return NULL; } // Overloadable $enumSpamTraining = ''; // Message Id / References / In-Reply-To @($sMessageId = $headers['message-id']); $body_append_text = array(); $body_append_html = array(); // [mdf]Check attached files before creating the ticket because we may need to overwrite the message-id // also store any contents of rfc822 files so we can include them after the body foreach ($message->files as $filename => $file) { /* @var $file ParserFile */ switch ($file->mime_type) { case 'message/rfc822': $full_filename = $file->tmpname; $mail = mailparse_msg_parse_file($full_filename); $struct = mailparse_msg_get_structure($mail); $msginfo = mailparse_msg_get_part_data($mail); $inline_headers = $msginfo['headers']; if (isset($headers['from']) && (strtolower(substr($headers['from'], 0, 11)) == 'postmaster@' || strtolower(substr($headers['from'], 0, 14)) == 'mailer-daemon@')) { $headers['in-reply-to'] = $inline_headers['message-id']; } break; } } // [JAS] [TODO] References header may contain multiple message-ids to find if (null != ($ids = self::findParentMessage($headers))) { $bIsNew = false; $id = $ids['ticket_id']; $msgid = $ids['message_id']; // Is it a worker reply from an external client? If so, proxy if (null != ($worker_address = DAO_AddressToWorker::getByAddress($fromAddressInst->email))) { $logger->info("[Parser] Handling an external worker response from " . $fromAddressInst->email); if (!DAO_Ticket::isTicketRequester($worker_address->address, $id)) { // Watcher Commands [TODO] Document on wiki/etc if (0 != ($matches = preg_match_all("/\\[(.*?)\\]/i", $message->headers['subject'], $commands))) { @($command = strtolower(array_pop($commands[1]))); $logger->info("[Parser] Worker command: " . $command); switch ($command) { case 'close': DAO_Ticket::updateTicket($id, array(DAO_Ticket::IS_CLOSED => CerberusTicketStatus::CLOSED)); break; case 'take': DAO_Ticket::updateTicket($id, array(DAO_Ticket::NEXT_WORKER_ID => $worker_address->worker_id)); break; case 'comment': $comment_id = DAO_TicketComment::create(array(DAO_TicketComment::ADDRESS_ID => $fromAddressInst->id, DAO_TicketComment::CREATED => time(), DAO_TicketComment::TICKET_ID => $id, DAO_TicketComment::COMMENT => $message->body)); return $id; break; default: // Typo? break; } } $attachment_files = array(); $attachment_files['name'] = array(); $attachment_files['type'] = array(); $attachment_files['tmp_name'] = array(); $attachment_files['size'] = array(); $i = 0; foreach ($message->files as $filename => $file) { $attachment_files['name'][$i] = $filename; $attachment_files['type'][$i] = $file->mime_type; $attachment_files['tmp_name'][$i] = $file->tmpname; $attachment_files['size'][$i] = $file->file_size; $i++; } CerberusMail::sendTicketMessage(array('message_id' => $msgid, 'content' => $message->body, 'files' => $attachment_files, 'agent_id' => $worker_address->worker_id)); return $id; } else { // ... worker is a requester, treat as normal $logger->info("[Parser] The external worker was a ticket requester, so we're not treating them as a watcher."); } } else { // Reply: Not sent by a worker /* * [TODO] check that this sender is a requester on the matched ticket * Otherwise blank out the $id */ } } $group_id = 0; if (empty($id)) { // New Ticket $sMask = CerberusApplication::generateTicketMask(); $groups = DAO_Group::getAll(); // Routing new tickets if (null != ($routing_rules = Model_MailToGroupRule::getMatches($fromAddressInst, $message))) { if (is_array($routing_rules)) { foreach ($routing_rules as $rule) { // Only end up with the last 'move' action (ignore the previous) if (isset($rule->actions['move'])) { $group_id = intval($rule->actions['move']['group_id']); // We don't need to move again when running rule actions unset($rule->actions['move']); } } } } // Make sure the group exists if (!isset($groups[$group_id])) { $group_id = null; } // Last ditch effort to check for a default group to deliver to if (empty($group_id)) { if (null != ($default_team = DAO_Group::getDefaultGroup())) { $group_id = $default_team->id; } else { // Bounce return null; } } // [JAS] It's important to not set the group_id on the ticket until the messages exist // or inbox filters will just abort. $fields = array(DAO_Ticket::MASK => $sMask, DAO_Ticket::SUBJECT => $sSubject, DAO_Ticket::IS_CLOSED => 0, DAO_Ticket::FIRST_WROTE_ID => intval($fromAddressInst->id), DAO_Ticket::LAST_WROTE_ID => intval($fromAddressInst->id), DAO_Ticket::CREATED_DATE => $iDate, DAO_Ticket::UPDATED_DATE => $iDate, DAO_Ticket::LAST_ACTION_CODE => CerberusTicketActionCode::TICKET_OPENED); $id = DAO_Ticket::createTicket($fields); // Apply routing actions to our new ticket ID if (isset($routing_rules) && is_array($routing_rules)) { foreach ($routing_rules as $rule) { $rule->run($id); } } } // [JAS]: Add requesters to the ticket if (!empty($fromAddressInst->id) && !empty($id)) { // Don't add a requester if the sender is a helpdesk address if (isset($helpdesk_senders[$fromAddressInst->email])) { $logger->info("[Parser] Not adding ourselves as a requester: " . $fromAddressInst->email); } else { DAO_Ticket::createRequester($fromAddressInst->id, $id); } } // Add the other TO/CC addresses to the ticket // [TODO] This should be cleaned up and optimized if ($settings->get('cerberusweb.core', CerberusSettings::PARSER_AUTO_REQ, 0)) { @($autoreq_exclude_list = $settings->get('cerberusweb.core', CerberusSettings::PARSER_AUTO_REQ_EXCLUDE, '')); $destinations = self::getDestinations($headers); if (is_array($destinations) && !empty($destinations)) { // Filter out any excluded requesters if (!empty($autoreq_exclude_list)) { @($autoreq_exclude = DevblocksPlatform::parseCrlfString($autoreq_exclude_list)); if (is_array($autoreq_exclude) && !empty($autoreq_exclude)) { foreach ($autoreq_exclude as $excl_pattern) { $excl_regexp = DevblocksPlatform::parseStringAsRegExp($excl_pattern); // Check all destinations for this pattern foreach ($destinations as $idx => $dest) { if (@preg_match($excl_regexp, $dest)) { unset($destinations[$idx]); } } } } } foreach ($destinations as $dest) { if (null != ($destInst = CerberusApplication::hashLookupAddress($dest, true))) { // Skip if the destination is one of our senders or the matching TO if (isset($helpdesk_senders[$destInst->email])) { continue; } DAO_Ticket::createRequester($destInst->id, $id); } } } } $attachment_path = APP_STORAGE_PATH . '/attachments/'; // [TODO] This should allow external attachments (S3) $fields = array(DAO_Message::TICKET_ID => $id, DAO_Message::CREATED_DATE => $iDate, DAO_Message::ADDRESS_ID => $fromAddressInst->id); $email_id = DAO_Message::create($fields); // Content DAO_MessageContent::create($email_id, $message->body); // Headers foreach ($headers as $hk => $hv) { DAO_MessageHeader::create($email_id, $hk, $hv); } // [mdf] Loop through files to insert attachment records in the db, and move temporary files if (!empty($email_id)) { foreach ($message->files as $filename => $file) { /* @var $file ParserFile */ //[mdf] skip rfc822 messages since we extracted their content above if ($file->mime_type == 'message/rfc822') { continue; } $fields = array(DAO_Attachment::MESSAGE_ID => $email_id, DAO_Attachment::DISPLAY_NAME => $filename, DAO_Attachment::MIME_TYPE => $file->mime_type, DAO_Attachment::FILE_SIZE => intval($file->file_size)); $file_id = DAO_Attachment::create($fields); if (empty($file_id)) { @unlink($file->tmpname); // remove our temp file continue; } // Make file attachments use buckets so we have a max per directory $attachment_bucket = sprintf("%03d/", mt_rand(1, 100)); $attachment_file = $file_id; if (!file_exists($attachment_path . $attachment_bucket)) { @mkdir($attachment_path . $attachment_bucket, 0770, true); // [TODO] Needs error checking } rename($file->getTempFile(), $attachment_path . $attachment_bucket . $attachment_file); // [TODO] Split off attachments into its own DAO DAO_Attachment::update($file_id, array(DAO_Attachment::FILEPATH => $attachment_bucket . $attachment_file)); } } // Pre-load custom fields if (isset($message->custom_fields) && !empty($message->custom_fields)) { foreach ($message->custom_fields as $cf_id => $cf_val) { if (is_array($cf_val) && !empty($cf_val) || !is_array($cf_val) && 0 != strlen($cf_val)) { DAO_CustomFieldValue::setFieldValue('cerberusweb.fields.source.ticket', $id, $cf_id, $cf_val); } } } // Finalize our new ticket details (post-message creation) if ($bIsNew && !empty($id) && !empty($email_id)) { // First thread (needed for anti-spam) DAO_Ticket::updateTicket($id, array(DAO_Ticket::FIRST_MESSAGE_ID => $email_id)); // Prime the change fields (which a few things like anti-spam might change before we commit) $change_fields = array(DAO_Ticket::TEAM_ID => $group_id); $out = CerberusBayes::calculateTicketSpamProbability($id); if (!empty($group_id)) { @($spam_threshold = DAO_GroupSettings::get($group_id, DAO_GroupSettings::SETTING_SPAM_THRESHOLD, 80)); @($spam_action = DAO_GroupSettings::get($group_id, DAO_GroupSettings::SETTING_SPAM_ACTION, '')); @($spam_action_param = DAO_GroupSettings::get($group_id, DAO_GroupSettings::SETTING_SPAM_ACTION_PARAM, '')); if ($out['probability'] * 100 >= $spam_threshold) { $enumSpamTraining = CerberusTicketSpamTraining::SPAM; switch ($spam_action) { default: case 0: // do nothing break; case 1: // delete $change_fields[DAO_Ticket::IS_CLOSED] = 1; $change_fields[DAO_Ticket::IS_DELETED] = 1; break; case 2: // move $buckets = DAO_Bucket::getAll(); // Verify bucket exists if (!empty($spam_action_param) && isset($buckets[$spam_action_param])) { $change_fields[DAO_Ticket::TEAM_ID] = $group_id; $change_fields[DAO_Ticket::CATEGORY_ID] = $spam_action_param; } break; } } } // end spam training // Save properties if (!empty($change_fields)) { DAO_Ticket::updateTicket($id, $change_fields); } } // Reply notifications (new messages are handled by 'move' listener) if (!$bIsNew) { // Inbound Reply Event $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('ticket.reply.inbound', array('ticket_id' => $id))); } // New ticket processing if ($bIsNew) { // Auto reply @($autoreply_enabled = DAO_GroupSettings::get($group_id, DAO_GroupSettings::SETTING_AUTO_REPLY_ENABLED, 0)); @($autoreply = DAO_GroupSettings::get($group_id, DAO_GroupSettings::SETTING_AUTO_REPLY, '')); /* * Send the group's autoreply if one exists, as long as this ticket isn't spam */ if (!isset($options['no_autoreply']) && $autoreply_enabled && !empty($autoreply) && $enumSpamTraining != CerberusTicketSpamTraining::SPAM) { CerberusMail::sendTicketMessage(array('ticket_id' => $id, 'message_id' => $email_id, 'content' => str_replace(array('#ticket_id#', '#mask#', '#subject#', '#timestamp#', '#sender#', '#sender_first#', '#orig_body#'), array($id, $sMask, $sSubject, date('r'), $fromAddressInst->email, $fromAddressInst->first_name, ltrim($message->body)), $autoreply), 'is_autoreply' => true, 'dont_keep_copy' => true)); } } // end bIsNew unset($message); // Re-open and update our date on new replies if (!$bIsNew) { DAO_Ticket::updateTicket($id, array(DAO_Ticket::UPDATED_DATE => time(), DAO_Ticket::IS_WAITING => 0, DAO_Ticket::IS_CLOSED => 0, DAO_Ticket::IS_DELETED => 0, DAO_Ticket::LAST_WROTE_ID => $fromAddressInst->id, DAO_Ticket::LAST_ACTION_CODE => CerberusTicketActionCode::TICKET_CUSTOMER_REPLY)); // [TODO] The TICKET_CUSTOMER_REPLY should be sure of this message address not being a worker } @imap_errors(); // Prevent errors from spilling out into STDOUT return $id; }
function saveContactAction() { $active_worker = CerberusApplication::getActiveWorker(); $db = DevblocksPlatform::getDatabaseService(); @($id = DevblocksPlatform::importGPC($_REQUEST['id'], 'integer', 0)); @($email = trim(DevblocksPlatform::importGPC($_REQUEST['email'], 'string', ''))); @($first_name = trim(DevblocksPlatform::importGPC($_REQUEST['first_name'], 'string', ''))); @($last_name = trim(DevblocksPlatform::importGPC($_REQUEST['last_name'], 'string', ''))); @($contact_org = trim(DevblocksPlatform::importGPC($_REQUEST['contact_org'], 'string', ''))); @($is_banned = DevblocksPlatform::importGPC($_REQUEST['is_banned'], 'integer', 0)); @($pass = DevblocksPlatform::importGPC($_REQUEST['pass'], 'string', '')); @($unregister = DevblocksPlatform::importGPC($_REQUEST['unregister'], 'integer', 0)); @($view_id = DevblocksPlatform::importGPC($_REQUEST['view_id'], 'string', '')); if ($active_worker->hasPriv('core.addybook.addy.actions.update')) { $contact_org_id = 0; if (!empty($contact_org)) { $contact_org_id = DAO_ContactOrg::lookup($contact_org, true); $contact_org = DAO_ContactOrg::get($contact_org_id); } // Common fields $fields = array(DAO_Address::FIRST_NAME => $first_name, DAO_Address::LAST_NAME => $last_name, DAO_Address::CONTACT_ORG_ID => $contact_org_id, DAO_Address::IS_BANNED => $is_banned); // Are we clearing the contact's login? if ($unregister) { $fields[DAO_Address::IS_REGISTERED] = 0; $fields[DAO_Address::PASS] = ''; } elseif (!empty($pass)) { // Are we changing their password? $fields[DAO_Address::IS_REGISTERED] = 1; $fields[DAO_Address::PASS] = md5($pass); } if ($id == 0) { $fields = $fields + array(DAO_Address::EMAIL => $email); $id = DAO_Address::create($fields); } else { DAO_Address::update($id, $fields); } // Custom field saves @($field_ids = DevblocksPlatform::importGPC($_POST['field_ids'], 'array', array())); DAO_CustomFieldValue::handleFormPost(ChCustomFieldSource_Address::ID, $id, $field_ids); /* * Notify anything that wants to know when Address Peek saves. */ $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('address.peek.saved', array('address_id' => $id, 'changed_fields' => $fields))); } if (!empty($view_id)) { $view = C4_AbstractViewLoader::getView($view_id); $view->render(); } }
static function sendMailProperties($properties) { $status = true; @($toStr = $properties['to']); @($cc = $properties['cc']); @($bcc = $properties['bcc']); @($subject = $properties['subject']); @($content = $properties['content']); @($files = $properties['files']); $mail_settings = self::getMailerDefaults(); if (empty($properties['from_addy'])) { @($from_addy = $settings->get('feg.core', FegSettings::DEFAULT_REPLY_FROM, $_SERVER['SERVER_ADMIN'])); } if (empty($properties['from_personal'])) { @($from_personal = $settings->get('feg.core', FegSettings::DEFAULT_REPLY_PERSONAL, '')); } if (empty($subject)) { $subject = '(no subject)'; } // [JAS]: Replace any semi-colons with commas (people like using either) $toList = DevblocksPlatform::parseCsvString(str_replace(';', ',', $toStr)); $mail_headers = array(); $mail_headers['X-FegCompose'] = '1'; // Headers needed for the ticket message $log_headers = new Swift_Message_Headers(); $log_headers->setCharset(LANG_CHARSET_CODE); $log_headers->set('To', $toList); $log_headers->set('From', !empty($from_personal) ? sprintf("%s <%s>", $from_personal, $from_addy) : sprintf('%s', $from_addy)); $log_headers->set('Subject', $subject); $log_headers->set('Date', date('r')); foreach ($log_headers->getList() as $hdr => $v) { if (null != ($hdr_val = $log_headers->getEncoded($hdr))) { if (!empty($hdr_val)) { $mail_headers[$hdr] = $hdr_val; } } } try { $mail_service = DevblocksPlatform::getMailService(); $mailer = $mail_service->getMailer(FegMail::getMailerDefaults()); $email = $mail_service->createMessage(); $email->setTo($toList); // cc $ccs = array(); if (!empty($cc) && null != ($ccList = DevblocksPlatform::parseCsvString(str_replace(';', ',', $cc)))) { $email->setCc($ccList); } // bcc if (!empty($bcc) && null != ($bccList = DevblocksPlatform::parseCsvString(str_replace(';', ',', $bcc)))) { $email->setBcc($bccList); } $email->setFrom(array($from => $personal)); $email->setSubject($subject); $email->generateId(); $headers = $email->getHeaders(); $headers->addTextHeader('X-Mailer', 'Fax Email Gateway (FEG) ' . APP_VERSION . ' (Build ' . APP_BUILD . ')'); $email->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; } $email->attach(Swift_Attachment::fromPath($file)->setFilename($files['name'][$idx])); } } // Headers foreach ($email->getHeaders()->getAll() as $hdr) { if (null != ($hdr_val = $hdr->getFieldBody())) { if (!empty($hdr_val)) { $mail_headers[$hdr->getFieldName()] = $hdr_val; } } } // [TODO] Allow separated addresses (parseRfcAddress) // $mailer->log->enable(); if (!@$mailer->send($email)) { throw new Exception('Mail failed to send: unknown reason'); } // $mailer->log->dump(); } catch (Exception $e) { // Do Something $status = false; } // Give plugins a chance to note a message is imported. $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('email.send', array('properties' => $properties, 'send_status' => $status ? 2 : 1))); return $status; }
static function create($fields) { $db = DevblocksPlatform::getDatabaseService(); $id = $db->GenID('ticket_comment_seq'); $sql = sprintf("INSERT INTO ticket_comment (id) " . "VALUES (%d)", $id); $db->Execute($sql); self::update($id, $fields); /* This event fires after the change takes place in the db, * which is important if the listener needs to stack changes */ if (!empty($fields[self::TICKET_ID]) && !empty($fields[self::ADDRESS_ID]) && !empty($fields[self::COMMENT])) { $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('ticket.comment.create', array('comment_id' => $id, 'ticket_id' => $fields[self::TICKET_ID], 'address_id' => $fields[self::ADDRESS_ID], 'comment' => $fields[self::COMMENT]))); } return $id; }
function setCustomerAccountNumberAction() { $db = DevblocksPlatform::getDatabaseService(); @($account_number = DevblocksPlatform::importGPC($_REQUEST['acc_num'], 'string', '')); @($m_id = DevblocksPlatform::importGPC($_REQUEST['m_id'], 'string', '')); // Now Confirm the account exists and is active $account = array_shift(DAO_CustomerAccount::getWhere(sprintf("%s = %d AND %s = '0' ", DAO_CustomerAccount::ACCOUNT_NUMBER, $account_number, DAO_CustomerAccount::IS_DISABLED))); if (!isset($account)) { return; } $message_obj = DAO_Message::get($m_id); if (!isset($message_obj)) { return; } $fields = get_object_vars($message_obj); $fields[DAO_Message::ACCOUNT_ID] = $account->id; $fields[DAO_Message::IMPORT_STATUS] = 0; // Requeue $m_status = DAO_Message::update($m_id, $fields); // Give plugins a chance to note a message is assigned $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('message.account.assign', array('account_id' => $account->id, 'message_id' => $m_id))); echo json_encode($fields); }
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))); } }
static function delete($ids) { if (!is_array($ids)) { $ids = array($ids); } $db = DevblocksPlatform::getDatabaseService(); /* * Notify anything that wants to know when buckets delete. */ $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('bucket.delete', array('bucket_ids' => $ids))); $sql = sprintf("DELETE QUICK FROM category WHERE id IN (%s)", implode(',', $ids)); $db->Execute($sql) or die(__CLASS__ . '(' . __LINE__ . ')' . ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */ // Reset any tickets using this category $sql = sprintf("UPDATE ticket SET category_id = 0 WHERE category_id IN (%s)", implode(',', $ids)); $db->Execute($sql) or die(__CLASS__ . '(' . __LINE__ . ')' . ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */ self::clearCache(); }
function ExportSnpp(Model_ExportType $export_type) { $logger = DevblocksPlatform::getConsoleLog(); $db = DevblocksPlatform::getDatabaseService(); @($snpp_current_hour = 0); @($snpp_sent_today = 0); $memory_limit = ini_get('memory_limit'); if (substr($memory_limit, 0, -1) < 128) { @ini_set('memory_limit', '128M'); } @set_time_limit(0); // Unlimited (if possible) $logger->info("[SNPP Exporter] Overloaded memory_limit to: " . ini_get('memory_limit')); $logger->info("[SNPP Exporter] Overloaded max_execution_time to: " . ini_get('max_execution_time')); $timeout = ini_get('max_execution_time'); $runtime = microtime(true); $sql = sprintf("SELECT mr.id " . "FROM message_recipient mr " . "inner join customer_recipient cr on mr.recipient_id = cr.id " . "WHERE mr.send_status in (0,3,4) " . "AND cr.is_disabled = 0 " . "AND cr.export_type = %d " . "AND cr.type = 2 ", $export_type->id); $rs = $db->Execute($sql); // Loop though pending outbound emails. while ($row = mysql_fetch_assoc($rs)) { $id = $row['id']; $logger->info("[SNPP Exporter] Procing MR ID: " . $id); $message_recipient = DAO_MessageRecipient::get($id); $message = DAO_Message::get($message_recipient->message_id); $message_lines = explode('\\n', substr($message->message, 1, -1)); $recipient = DAO_CustomerRecipient::get($message_recipient->recipient_id); $message_str = substr(implode("", $message_lines), 0, 160); // FIXME - Need to add in filter for now everything is unfiltered. // sendSnpp($phone_number, $message, $snpp_server="ann100sms01.answernet.com", $port=444) $send_status = FegSnpp::sendSnpp($recipient->address, $message_str); $logger->info("[SNPP Exporter] Send Status: " . ($send_status ? "Successful" : "Failure")); // Give plugins a chance to run export $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('cron.send.snpp', array('recipient' => $recipient, 'message' => $message, 'message_lines' => $message_lines, 'message_recipient' => $message_recipient, 'send_status' => $send_status))); if ($send_status) { $snpp_current_hour++; $snpp_sent_today++; } $fields = array(DAO_MessageRecipient::SEND_STATUS => $send_status ? 2 : 1, DAO_MessageRecipient::CLOSED_DATE => $send_status ? time() : 0); DAO_MessageRecipient::update($id, $fields); } mysql_free_result($rs); if ($snpp_current_hour) { $current_fields = DAO_Stats::get(0); $fields = array(DAO_Stats::SNPP_CURRENT_HOUR => $current_fields->snpp_current_hour + $snpp_current_hour, DAO_Stats::SNPP_SENT_TODAY => $current_fields->snpp_sent_today + $snpp_sent_today); DAO_Stats::update(0, $fields); } return NULL; }
private function _handleTicketMoved($event) { @($ticket_ids = $event->params['ticket_ids']); @($changed_fields = $event->params['changed_fields']); if (!isset($changed_fields[DAO_Ticket::TEAM_ID])) { return; } @($team_id = $changed_fields[DAO_Ticket::TEAM_ID]); @($bucket_id = $changed_fields[DAO_Ticket::CATEGORY_ID]); $final_moves = array(); // If we're landing in an inbox we need to check its filters if (!empty($ticket_ids) && !empty($team_id) && empty($bucket_id)) { // moving to an inbox if (is_array($ticket_ids)) { foreach ($ticket_ids as $ticket_id) { // Run the new inbox filters $matches = CerberusApplication::runGroupRouting($team_id, $ticket_id); // If we matched no rules, we're stuck in the destination inbox. if (empty($matches)) { $final_moves[] = $ticket_id; } else { // If more inbox rules want to move this ticket don't consider this finished if (is_array($matches)) { foreach ($matches as $match) { if (isset($match->actions['move'])) { // any moves break; } $final_moves[] = $ticket_id; } } } } } // Anything dropping into a non-inbox bucket is done chain-moving } elseif (!empty($ticket_ids) && !empty($team_id) && !empty($bucket_id)) { $final_moves = $ticket_ids; } // Did we have any tickets finish chain-moving? if (!empty($final_moves)) { // Trigger an inbound event for all moves if (is_array($final_moves)) { foreach ($final_moves as $ticket_id) { // Inbound Reply Event $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('ticket.reply.inbound', array('ticket_id' => $ticket_id))); } } } }
function setMessageStatusAction() { $translate = DevblocksPlatform::getTranslationService(); @($id = DevblocksPlatform::importGPC($_REQUEST['id'], 'integer', 0)); @($view_id = DevblocksPlatform::importGPC($_REQUEST['view_id'], 'string', '')); @($status = DevblocksPlatform::importGPC($_REQUEST['status'], 'integer', 0)); @($goto_recent = DevblocksPlatform::importGPC($_REQUEST['goto_recent'], 'integer', 0)); $message_obj = DAO_Message::get($id); $fields = get_object_vars($message_obj); $fields[DAO_Message::IMPORT_STATUS] = $status; $mr_status = DAO_Message::update($id, $fields); // Give plugins a chance to note a message is imported. $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger(new Model_DevblocksEvent('message.status', array('message_id' => $id, 'account_id' => $message_obj->account_id, 'import_status' => $status))); $status_text = $translate->_('feg.message.import_status_' . $status); if ($status_text == "") { $status_text = $translate->_('feg.message_recipient.status_unknown'); } echo $status_text; }