\r\n"; echo 'Memory Limit: ', ini_get('memory_limit') ,"
\r\n"; $runtime = microtime(true); echo "
\r\n"; flush(); $total = $this->getParam('max_messages', 500); $mailDir = APP_MAIL_PATH . 'new' . DIRECTORY_SEPARATOR; $subdirs = glob($mailDir . '*', GLOB_ONLYDIR); $subdirs[] = $mailDir; // Add our root directory last foreach($subdirs as $subdir) { if(!is_writable($subdir)) { echo 'Write permission error, unable parse messages inside: '. $subdir. "...skipping
\r\n"; continue; } $files = $this->scanDirMessages($subdir); foreach($files as $file) { $filePart = basename($file); $parseFile = APP_MAIL_PATH . 'fail' . DIRECTORY_SEPARATOR . $filePart; rename($file, $parseFile); $this->_parseFile($parseFile); flush(); if(--$total <= 0) break; } if($total <= 0) break; } unset($files); unset($subdirs); echo "Total Runtime: ",((microtime(true)-$runtime)*1000)," ms
\r\n"; } function _parseFile($full_filename) { $fileparts = pathinfo($full_filename); echo "Reading ",$fileparts['basename'],"...
\r\n"; echo "Decoding... "; //flush(); $time = microtime(true); $mime = mailparse_msg_parse_file($full_filename); $message = CerberusParser::parseMime($mime, $full_filename); $time = microtime(true) - $time; echo "decoded! (",sprintf("%d",($time*1000))," ms)
\r\n"; //flush(); // echo "Plaintext: ", $message->body,"
"; // echo "
"; // echo "HTML: ", htmlentities($message->htmlbody), "
"; // echo "
"; // echo "Files: "; print_r($message->files); echo "
"; // echo "
"; echo "Parsing... ";//flush(); $time = microtime(true); $ticket_id = CerberusParser::parseMessage($message); $time = microtime(true) - $time; echo "parsed! (",sprintf("%d",($time*1000))," ms) (Ticket ID: ",$ticket_id,")
\r\n"; @unlink($full_filename); mailparse_msg_free($mime); echo "
"; flush(); } function configure($instance) { $tpl = DevblocksPlatform::getTemplateService(); $tpl->cache_lifetime = "0"; $tpl_path = dirname(__FILE__) . '/templates/'; $tpl->assign('path', $tpl_path); $tpl->assign('max_messages', $this->getParam('max_messages', 500)); $tpl->display($tpl_path . 'cron/parser/config.tpl.php'); } function saveConfigurationAction() { @$max_messages = DevblocksPlatform::importGPC($_POST['max_messages'],'integer'); $this->setParam('max_messages', $max_messages); } }; // [TODO] Clear idle temp files (fileatime()) class MaintCron extends CerberusCronPageExtension { function run() { @ini_set('memory_limit','64M'); $db = DevblocksPlatform::getDatabaseService(); // Purge Deleted Content $purged = 0; $max_purges = 2500; // max per maint run // [TODO] Make this configurable from job $purge_waitdays = intval($this->getParam('purge_waitdays', 7)); $purge_waitsecs = $purge_waitdays*24*60*60; do { list($tickets, $null) = DAO_Ticket::search( array(), array( new DevblocksSearchCriteria(SearchFields_Ticket::TICKET_DELETED,'=',1), new DevblocksSearchCriteria(SearchFields_Ticket::TICKET_UPDATED_DATE,DevblocksSearchCriteria::OPER_LT,time()-$purge_waitsecs) ), 250, 0, SearchFields_Ticket::TICKET_LAST_WROTE, 0, false ); if(!empty($tickets)) { $purged += count($tickets); DAO_Ticket::delete(array_keys($tickets)); } } while(!empty($tickets) && ($purged < $max_purges)); echo "Deleted ", $purged, " tickets!
\r\n"; // Nuke orphaned words from the Bayes index // [TODO] Make this configurable from job $sql = "DELETE FROM bayes_words WHERE nonspam + spam < 2"; // only 1 occurrence $db->Execute($sql); // [TODO] After deletion, check for any leftover NULL rows and delete them // [mdf] Remove any empty directories inside storage/mail/new $mailDir = APP_MAIL_PATH . 'new' . DIRECTORY_SEPARATOR; $subdirs = glob($mailDir . '*', GLOB_ONLYDIR); foreach($subdirs as $subdir) { $directory_empty = count(glob($subdir. DIRECTORY_SEPARATOR . '*')) === 0; if($directory_empty && is_writeable($subdir)) { rmdir($subdir); } } // Recover any tickets assigned to a NULL bucket $sql = "SELECT DISTINCT t.category_id as id ". "FROM ticket t ". "LEFT JOIN category c ON (t.category_id=c.id) ". "WHERE c.id IS NULL AND t.category_id > 0"; $rs = $db->Execute($sql); while(!$rs->EOF) { $sql = sprintf("UPDATE ticket SET category_id = 0 WHERE category_id = %d", $rs->fields['id'] ); $db->Execute($sql); $rs->MoveNext(); } // Optimize/Vaccuum // [TODO] Make this configurable from job $perf = NewPerfMonitor($db); // $perf->optimizeDatabase(); } function configure($instance) { $tpl = DevblocksPlatform::getTemplateService(); $tpl->cache_lifetime = "0"; $tpl_path = dirname(__FILE__) . '/templates/'; $tpl->assign('path', $tpl_path); $tpl->assign('purge_waitdays', $this->getParam('purge_waitdays', 7)); $tpl->display($tpl_path . 'cron/maint/config.tpl.php'); } function saveConfigurationAction() { @$purge_waitdays = DevblocksPlatform::importGPC($_POST['purge_waitdays'],'integer'); $this->setParam('purge_waitdays', $purge_waitdays); } }; /** * Plugins can implement an event listener on the heartbeat to do any kind of * time-dependent or interval-based events. For example, doing a workflow * action every 5 minutes. */ class HeartbeatCron extends CerberusCronPageExtension { function run() { // Heartbeat Event $eventMgr = DevblocksPlatform::getEventService(); $eventMgr->trigger( new Model_DevblocksEvent( 'cron.heartbeat', array( ) ) ); } function configure($instance) { $tpl = DevblocksPlatform::getTemplateService(); $tpl->cache_lifetime = "0"; $tpl_path = dirname(__FILE__) . '/templates/'; $tpl->assign('path', $tpl_path); $tpl->display($tpl_path . 'cron/heartbeat/config.tpl.php'); } }; class Pop3Cron extends CerberusCronPageExtension { function run() { if (!extension_loaded("imap")) die("IMAP Extension not loaded!"); @set_time_limit(0); // Unlimited (if possible) @ini_set('memory_limit','64M'); $accounts = DAO_Mail::getPop3Accounts(); /* @var $accounts CerberusPop3Account[] */ $timeout = ini_get('max_execution_time'); $max_downloads = $this->getParam('max_messages', (($timeout) ? 20 : 50)); // [JAS]: Make sure our output directory is writeable if(!is_writable(APP_MAIL_PATH . 'new' . DIRECTORY_SEPARATOR)) { echo "The mail storage directory is not writeable. Skipping POP3 download.
\r\n"; return; } foreach ($accounts as $account) { /* @var $account CerberusPop3Account */ if(!$account->enabled) continue; echo 'Account being parsed is ', $account->nickname, "
\r\n"; echo 'Time Limit: ', (($timeout) ? $timeout : 'unlimited') ," secs
\r\n"; echo 'Memory Limit: ', ini_get('memory_limit') ,"
\r\n"; switch($account->protocol) { default: case 'pop3': // 110 $connect = sprintf("{%s:%d/pop3/notls}INBOX", $account->host, $account->port ); break; case 'pop3-ssl': // 995 $connect = sprintf("{%s:%d/pop3/ssl/novalidate-cert}INBOX", $account->host, $account->port ); break; case 'imap': // 143 $connect = sprintf("{%s:%d/notls}INBOX", $account->host, $account->port ); break; case 'imap-ssl': // 993 $connect = sprintf("{%s:%d/imap/ssl/novalidate-cert}INBOX", $account->host, $account->port ); break; } $runtime = microtime(true); $mailbox = imap_open($connect, !empty($account->username)?$account->username:"", !empty($account->password)?$account->password:"") or die("Failed with error: ".imap_last_error()); $messages = array(); $check = imap_check($mailbox); // [TODO] Make this an account setting? $total = min($max_downloads,$check->Nmsgs); echo 'Init time: ',((microtime(true)-$runtime)*1000)," ms
\r\n"; echo "
\r\n"; flush(); $runtime = microtime(true); for($i=1;$i<=$total;$i++) { /* * [TODO] Logic for max message size (>1MB, etc.) handling. If over a * threshold then use the attachment parser (imap_fetchstructure) to toss * non-plaintext until the message fits. */ $msgno = $i; echo "Downloading message ",$msgno," "; flush(); $time = microtime(true); $headers = imap_fetchheader($mailbox, $msgno); $body = imap_body($mailbox, $msgno); do { $unique = sprintf("%s.%04d", time(), rand(0,9999) ); $filename = APP_MAIL_PATH . 'new' . DIRECTORY_SEPARATOR . $unique; } while(file_exists($filename)); $fp = fopen($filename,'w'); if($fp) { fwrite($fp,$headers,strlen($headers)); fwrite($fp,"\r\n\r\n"); fwrite($fp,$body,strlen($body)); @fclose($fp); } /* * [JAS]: We don't add the .msg extension until we're done with the file, * since this will safely be ignored by the parser until we're ready * for it. */ rename($filename, dirname($filename) .DIRECTORY_SEPARATOR . basename($filename) . '.msg'); unset($headers); unset($body); $time = microtime(true) - $time; echo "(",sprintf("%d",($time*1000))," ms)
\r\n"; flush(); imap_delete($mailbox, $msgno); continue; } imap_expunge($mailbox); imap_close($mailbox); imap_errors(); echo "Total Runtime: ",((microtime(true)-$runtime)*1000)," ms
\r\n"; } } function configure($instance) { $tpl = DevblocksPlatform::getTemplateService(); $tpl->cache_lifetime = "0"; $tpl_path = dirname(__FILE__) . '/templates/'; $tpl->assign('path', $tpl_path); $timeout = ini_get('max_execution_time'); $tpl->assign('max_messages', $this->getParam('max_messages', (($timeout) ? 20 : 50))); $tpl->display($tpl_path . 'cron/pop3/config.tpl.php'); } function saveConfigurationAction() { @$max_messages = DevblocksPlatform::importGPC($_POST['max_messages'],'integer'); $this->setParam('max_messages', $max_messages); DevblocksPlatform::setHttpResponse(new DevblocksHttpResponse(array('config','jobs'))); } }; class ParserFile { public $tmpname = null; public $mime_type = ''; public $file_size = 0; public function setTempFile($tmpname,$mimetype='application/octet-stream') { $this->mime_type = $mimetype; if(!empty($tmpname) && file_exists($tmpname)) { $this->tmpname = $tmpname; } } public function getTempFile() { return $this->tmpname; } static public function makeTempFilename() { $path = DEVBLOCKS_PATH . 'tmp' . DIRECTORY_SEPARATOR; return tempnam($path,'mime'); } }; class ParseCronFileBuffer extends ParserFile { private $mime_filename = ''; private $section = null; private $info = array(); private $fp = null; function __construct($section, $info, $mime_filename) { $this->mime_filename = $mime_filename; $this->section = $section; $this->info = $info; $this->setTempFile(ParserFile::makeTempFilename(),@$info['content-type']); $this->fp = fopen($this->getTempFile(),'wb'); if($this->fp && !empty($this->section) && !empty($this->mime_filename)) { mailparse_msg_extract_part_file($this->section, $this->mime_filename, array($this, "writeCallback")); } @fclose($this->fp); } function writeCallback($chunk) { $this->file_size += fwrite($this->fp, $chunk); // echo $chunk; } } ?>