function downloadFileAction(DevblocksHttpRequest $request) { $umsession = UmPortalHelper::getSession(); $stack = $request->path; if (null == ($active_user = $umsession->getProperty('sc_login', null))) { return; } // Attachment ID + display name @($ticket_mask = array_shift($stack)); @($hash = array_shift($stack)); @($display_name = array_shift($stack)); if (empty($ticket_mask) || empty($hash) || empty($display_name)) { return; } if (null == ($ticket_id = DAO_Ticket::getTicketIdByMask($ticket_mask))) { return; } // Load attachments by ticket mask list($attachments) = DAO_Attachment::search(array(SearchFields_Attachment::TICKET_MASK => new DevblocksSearchCriteria(SearchFields_Attachment::TICKET_MASK, '=', $ticket_mask)), -1, 0, null, null, false); $attachment = null; if (is_array($attachments)) { foreach ($attachments as $possible_file) { // Compare the hash $fingerprint = md5($possible_file[SearchFields_Attachment::ID] . $possible_file[SearchFields_Attachment::MESSAGE_ID] . $possible_file[SearchFields_Attachment::DISPLAY_NAME]); if (0 == strcmp($fingerprint, $hash)) { if (null == ($attachment = DAO_Attachment::get($possible_file[SearchFields_Attachment::ID]))) { return; } break; } } } // No hit (bad hash) if (null == $attachment) { return; } // Load requesters if (null == ($requesters = DAO_Ticket::getRequestersByTicket($ticket_id))) { return; } // Security: Make sure the active user is a requester on the proper ticket if (!isset($requesters[$active_user->id])) { return; } // Set headers header("Expires: Mon, 26 Nov 1962 00:00:00 GMT\n"); header("Last-Modified: " . gmdate("D,d M YH:i:s") . " GMT\n"); header("Cache-control: private\n"); header("Pragma: no-cache\n"); header("Content-Type: " . $attachment->mime_type . "\n"); header("Content-transfer-encoding: binary\n"); header("Content-Length: " . $attachment->getFileSize() . "\n"); // Dump contents echo $attachment->getFileContents(); exit; }
private function _handleImportTicket($xml) { $settings = CerberusSettings::getInstance(); $logger = DevblocksPlatform::getConsoleLog(); $workers = DAO_Worker::getAll(); static $email_to_worker_id = null; static $group_name_to_id = null; static $bucket_name_to_id = null; // Hash Workers so we can ID their incoming tickets if (null == $email_to_worker_id) { $email_to_worker_id = array(); if (is_array($workers)) { foreach ($workers as $worker) { /* @var $worker CerberusWorker */ $email_to_worker_id[strtolower($worker->email)] = intval($worker->id); } } } // Hash Group names if (null == $group_name_to_id) { $groups = DAO_Group::getAll(); $group_name_to_id = array(); if (is_array($groups)) { foreach ($groups as $group) { $group_name_to_id[strtolower($group->name)] = intval($group->id); } } } // Hash Bucket names if (null == $bucket_name_to_id) { $buckets = DAO_Bucket::getAll(); $bucket_name_to_id = array(); if (is_array($buckets)) { foreach ($buckets as $bucket) { /* @var $bucket CerberusCategory */ // Hash by team ID and bucket name $hash = md5($bucket->team_id . strtolower($bucket->name)); $bucket_to_id[$hash] = intval($bucket->id); } } } $sMask = (string) $xml->mask; $sSubject = substr((string) $xml->subject, 0, 255); $sGroup = (string) $xml->group; $sBucket = (string) $xml->bucket; $iCreatedDate = (int) $xml->created_date; $iUpdatedDate = (int) $xml->updated_date; $isWaiting = (int) $xml->is_waiting; $isClosed = (int) $xml->is_closed; if (empty($sMask)) { $sMask = CerberusApplication::generateTicketMask(); } // Find the destination Group + Bucket (or create them) if (empty($sGroup)) { $iDestGroupId = 0; if (null != ($iDestGroup = DAO_Group::getDefaultGroup())) { $iDestGroupId = $iDestGroup->id; } } elseif (null == ($iDestGroupId = @$group_name_to_id[strtolower($sGroup)])) { $iDestGroupId = DAO_Group::createTeam(array(DAO_Group::TEAM_NAME => $sGroup)); // Give all superusers manager access to this new group if (is_array($workers)) { foreach ($workers as $worker) { if ($worker->is_superuser) { DAO_Group::setTeamMember($iDestGroupId, $worker->id, true); } } } // Rehash DAO_Group::getAll(true); $group_name_to_id[strtolower($sGroup)] = $iDestGroupId; } if (empty($sBucket)) { $iDestBucketId = 0; // Inbox } elseif (null == ($iDestBucketId = @$bucket_name_to_id[md5($iDestGroupId . strtolower($sBucket))])) { $iDestBucketId = DAO_Bucket::create($sBucket, $iDestGroupId); // Rehash DAO_Bucket::getAll(true); $bucket_name_to_id[strtolower($sBucket)] = $iDestBucketId; } // Xpath the first and last "from" out of "/ticket/messages/message/headers/from" $aMessageNodes = $xml->xpath("/ticket/messages/message"); $iNumMessages = count($aMessageNodes); @($eFirstMessage = reset($aMessageNodes)); if (is_null($eFirstMessage)) { $logger->warn('[Importer] Ticket ' . $sMask . " doesn't have any messages. Skipping."); return false; } if (is_null($eFirstMessage->headers) || is_null($eFirstMessage->headers->from)) { $logger->warn('[Importer] Ticket ' . $sMask . " first message doesn't provide a sender address."); return false; } $sFirstWrote = self::_parseRfcAddressList($eFirstMessage->headers->from, true); if (null == ($firstWroteInst = CerberusApplication::hashLookupAddress($sFirstWrote, true))) { $logger->warn('[Importer] Ticket ' . $sMask . " - Invalid sender adddress: " . $sFirstWrote); return false; } $eLastMessage = end($aMessageNodes); if (is_null($eLastMessage)) { $logger->warn('[Importer] Ticket ' . $sMask . " doesn't have any messages. Skipping."); return false; } if (is_null($eLastMessage->headers) || is_null($eLastMessage->headers->from)) { $logger->warn('[Importer] Ticket ' . $sMask . " last message doesn't provide a sender address."); return false; } $sLastWrote = self::_parseRfcAddressList($eLastMessage->headers->from, true); if (null == ($lastWroteInst = CerberusApplication::hashLookupAddress($sLastWrote, true))) { $logger->warn('[Importer] Ticket ' . $sMask . ' last message has an invalid sender address: ' . $sLastWrote); return false; } // Last action code + last worker $sLastActionCode = CerberusTicketActionCode::TICKET_OPENED; $iLastWorkerId = 0; if ($iNumMessages > 1) { if (null != @($iLastWorkerId = $email_to_worker_id[strtolower($lastWroteInst->email)])) { $sLastActionCode = CerberusTicketActionCode::TICKET_WORKER_REPLY; } else { $sLastActionCode = CerberusTicketActionCode::TICKET_CUSTOMER_REPLY; $iLastWorkerId = 0; } } // Dupe check by ticket mask if (null != DAO_Ticket::getTicketByMask($sMask)) { $logger->warn("[Importer] Ticket mask '" . $sMask . "' already exists. Making it unique."); $uniqueness = 1; $origMask = $sMask; // Append new uniqueness to the ticket mask: LLL-NNNNN-NNN-1, LLL-NNNNN-NNN-2, ... do { $sMask = $origMask . '-' . ++$uniqueness; } while (null != DAO_Ticket::getTicketIdByMask($sMask)); $logger->info("[Importer] The unique mask for '" . $origMask . "' is now '" . $sMask . "'"); } // Create ticket $fields = array(DAO_Ticket::MASK => $sMask, DAO_Ticket::SUBJECT => $sSubject, DAO_Ticket::IS_WAITING => $isWaiting, DAO_Ticket::IS_CLOSED => $isClosed, DAO_Ticket::FIRST_WROTE_ID => intval($firstWroteInst->id), DAO_Ticket::LAST_WROTE_ID => intval($lastWroteInst->id), DAO_Ticket::CREATED_DATE => $iCreatedDate, DAO_Ticket::UPDATED_DATE => $iUpdatedDate, DAO_Ticket::TEAM_ID => intval($iDestGroupId), DAO_Ticket::CATEGORY_ID => intval($iDestBucketId), DAO_Ticket::LAST_ACTION_CODE => $sLastActionCode, DAO_Ticket::LAST_WORKER_ID => intval($iLastWorkerId)); $ticket_id = DAO_Ticket::createTicket($fields); // echo "Ticket: ",$ticket_id,"<BR>"; // print_r($fields); // Create requesters if (!is_null($xml->requesters)) { foreach ($xml->requesters->address as $eAddress) { /* @var $eAddress SimpleXMLElement */ $sRequesterAddy = (string) $eAddress; // [TODO] RFC822 // Insert requesters if (null == ($requesterAddyInst = CerberusApplication::hashLookupAddress($sRequesterAddy, true))) { $logger->warn('[Importer] Ticket ' . $sMask . ' - Ignoring malformed requester: ' . $sRequesterAddy); continue; } DAO_Ticket::createRequester($requesterAddyInst->id, $ticket_id); } } // Create messages $is_first = true; if (!is_null($xml->messages)) { foreach ($xml->messages->message as $eMessage) { /* @var $eMessage SimpleXMLElement */ $eHeaders =& $eMessage->headers; /* @var $eHeaders SimpleXMLElement */ $sMsgFrom = (string) $eHeaders->from; $sMsgDate = (string) $eHeaders->date; $sMsgFrom = self::_parseRfcAddressList($sMsgFrom, true); if (NULL == $sMsgFrom) { $logger->warn('[Importer] Ticket ' . $sMask . ' - Invalid message sender: ' . $sMsgFrom . ' (skipping)'); continue; } if (null == ($msgFromInst = CerberusApplication::hashLookupAddress($sMsgFrom, true))) { $logger->warn('[Importer] Ticket ' . $sMask . ' - Invalid message sender: ' . $sMsgFrom . ' (skipping)'); continue; } @($msgWorkerId = intval($email_to_worker_id[strtolower($msgFromInst->email)])); // $logger->info('Checking if '.$msgFromInst->email.' is a worker'); $fields = array(DAO_Message::TICKET_ID => $ticket_id, DAO_Message::CREATED_DATE => strtotime($sMsgDate), DAO_Message::ADDRESS_ID => $msgFromInst->id, DAO_Message::IS_OUTGOING => !empty($msgWorkerId) ? 1 : 0, DAO_Message::WORKER_ID => !empty($msgWorkerId) ? $msgWorkerId : 0); $email_id = DAO_Message::create($fields); // First thread if ($is_first) { DAO_Ticket::updateTicket($ticket_id, array(DAO_Ticket::FIRST_MESSAGE_ID => $email_id)); $is_first = false; } // Create attachments if (!is_null($eMessage->attachments)) { foreach ($eMessage->attachments->attachment as $eAttachment) { /* @var $eAttachment SimpleXMLElement */ $sFileName = (string) $eAttachment->name; $sMimeType = (string) $eAttachment->mimetype; $sFileSize = (int) $eAttachment->size; $sFileContentB64 = (string) $eAttachment->content; // [TODO] This could be a little smarter about detecting extensions if (empty($sMimeType)) { $sMimeType = "application/octet-stream"; } $sFileContent = base64_decode($sFileContentB64); unset($sFileContentB64); $fields = array(DAO_Attachment::MESSAGE_ID => $email_id, DAO_Attachment::DISPLAY_NAME => $sFileName, DAO_Attachment::FILE_SIZE => intval($sFileSize), DAO_Attachment::FILEPATH => '', DAO_Attachment::MIME_TYPE => $sMimeType); $file_id = DAO_Attachment::create($fields); // Write file to disk using ID (Model) $file_path = Model_Attachment::saveToFile($file_id, $sFileContent); unset($sFileContent); // Update attachment table DAO_Attachment::update($file_id, array(DAO_Attachment::FILEPATH => $file_path)); } } // Create message content $sMessageContentB64 = (string) $eMessage->content; $sMessageContent = base64_decode($sMessageContentB64); // Content-type specific handling if (isset($eMessage->content['content-type'])) { // do we have a content-type? if (strtolower($eMessage->content['content-type']) == 'html') { // html? // Force to plaintext part $sMessageContent = CerberusApplication::stripHTML($sMessageContent); } } unset($sMessageContentB64); DAO_MessageContent::create($email_id, $sMessageContent); unset($sMessageContent); // Headers foreach ($eHeaders->children() as $eHeader) { /* @var $eHeader SimpleXMLElement */ DAO_MessageHeader::create($email_id, $eHeader->getName(), (string) $eHeader); } } } // Create comments if (!is_null($xml->comments)) { foreach ($xml->comments->comment as $eComment) { /* @var $eMessage SimpleXMLElement */ $iCommentDate = (int) $eComment->created_date; $sCommentAuthor = (string) $eComment->author; // [TODO] Address Hash Lookup $sCommentTextB64 = (string) $eComment->content; $sCommentText = base64_decode($sCommentTextB64); unset($sCommentTextB64); $commentAuthorInst = CerberusApplication::hashLookupAddress($sCommentAuthor, true); // [TODO] Sanity checking $fields = array(DAO_TicketComment::TICKET_ID => intval($ticket_id), DAO_TicketComment::CREATED => intval($iCommentDate), DAO_TicketComment::ADDRESS_ID => intval($commentAuthorInst->id), DAO_TicketComment::COMMENT => $sCommentText); $comment_id = DAO_TicketComment::create($fields); unset($sCommentText); } } $logger->info('[Importer] Imported ticket #' . $ticket_id); return true; }
/** * Looks up a ticket ID by the provided mask using a revolving cache. * This is useful if you need to translate several ticket masks into * IDs where there may be a lot of redundancy (batches in the e-mail * parser, etc.) * * @param string $mask The ticket mask to look up * @return integer The ticket id, or NULL if not found * * @todo [JAS]: Move this to a global cache/hash registry */ public static function hashLookupTicketIdByMask($mask) { static $hash_mask_to_id = array(); static $hash_hits = array(); static $hash_size = 0; if (isset($hash_mask_to_id[$mask])) { $return = $hash_mask_to_id[$mask]; @($hash_hits[$mask] = intval($hash_hits[$mask]) + 1); $hash_size++; // [JAS]: if our hash grows past our limit, crop hits array + intersect keys if ($hash_size > 250) { arsort($hash_hits); $hash_hits = array_slice($hash_hits, 0, 100, true); $hash_mask_to_id = array_intersect_key($hash_mask_to_id, $hash_hits); $hash_size = count($hash_mask_to_id); } return $return; } $ticket_id = DAO_Ticket::getTicketIdByMask($mask); if (!empty($ticket_id)) { $hash_mask_to_id[$mask] = $ticket_id; } return $ticket_id; }
function browseAction() { $translate = DevblocksPlatform::getTranslationService(); $visit = CerberusApplication::getVisit(); /* @var $visit CerberusVisit */ $request = DevblocksPlatform::getHttpRequest(); $stack = $request->path; array_shift($stack); // display array_shift($stack); // browse @($id = array_shift($stack)); // [JAS]: Mask if (!is_numeric($id)) { $id = DAO_Ticket::getTicketIdByMask($id); } $ticket = DAO_Ticket::getTicket($id); if (empty($ticket)) { echo "<H1>" . $translate->_('display.invalid_ticket') . "</H1>"; return; } // Display series support (inherited paging from Display) @($view_id = array_shift($stack)); if (!empty($view_id)) { $view = C4_AbstractViewLoader::getView('', $view_id); // Restrict to the active worker's groups $active_worker = CerberusApplication::getActiveWorker(); $memberships = $active_worker->getMemberships(); $view->params['tmp'] = new DevblocksSearchCriteria(SearchFields_Ticket::TICKET_TEAM_ID, 'in', array_keys($memberships)); $range = 250; // how far to jump ahead of the current page $block_size = 250; $page = floor($view->renderPage * $view->renderLimit / $block_size); $index = array(); $found = false; $full = false; do { list($series, $null) = DAO_Ticket::search(array(SearchFields_Ticket::TICKET_MASK), $view->params, $block_size, $page, $view->renderSortBy, $view->renderSortAsc, false); // Index by mask foreach ($series as $idx => $val) { // Find our match before we index anything if (!$found && $idx == $id) { $found = true; } elseif (!$found) { // Only keep a max of X things behind our match, reserve the most room ahead if (count($index) == 20) { array_shift($index); } } $index[] = $val[SearchFields_Ticket::TICKET_MASK]; // Stop if we fill up our desired rows if (count($index) == $range) { $full = true; break; } } $page++; } while (!empty($series) && !$full); $series_info = array('title' => $view->name, 'total' => count($index), 'series' => $index); $visit->set('ch_display_series', $series_info); } DevblocksPlatform::redirect(new DevblocksHttpResponse(array('display', $ticket->mask))); }
function render() { $tpl = DevblocksPlatform::getTemplateService(); $response = DevblocksPlatform::getHttpResponse(); @($ticket_id = $response->path[2]); @($page_type = DevblocksPlatform::importGPC($_REQUEST['page_type'], 'string', 'reply')); $message_id = $response->path[3]; if (empty($ticket_id)) { $session = DevblocksPlatform::getSessionService(); $visit = $session->getVisit(); return; } if (!is_numeric($ticket_id)) { $ticket_id = DAO_Ticket::getTicketIdByMask($ticket_id); } $ticket = DAO_Ticket::getTicket($ticket_id); $tpl->assign('ticket', $ticket); $tpl->assign('ticket_id', $ticket_id); $tpl->assign('message_id', $message_id); $tpl->assign('page_type', $page_type); if (0 == strcasecmp($message_id, 'full')) { $tpl->display('file:' . dirname(__FILE__) . '/templates/display.tpl'); } else { $message = DAO_Ticket::getMessage($message_id); if (empty($message)) { $message = array_pop($ticket->getMessages()); } $tpl->assign('message', $message); $tpl->display('file:' . dirname(__FILE__) . '/templates/display_brief.tpl'); } }
protected function getAction($path, $keychain) { if (Model_WebapiKey::ACL_NONE == intval(@$keychain->rights['acl_tickets'])) { $this->_error("Action not permitted."); } // Single GET if (1 == count($path) && is_numeric($path[0])) { $this->_getIdAction($path); } // Actions $value = array_shift($path); switch ($value) { case 'list': $this->_getListAction($path); break; default: if (($id = DAO_Ticket::getTicketIdByMask($value)) != null) { $this->_getIdAction(array($id)); } break; } }