\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;
}
}
?>