mailhandler.retrieve.inc in Mailhandler 6
Same filename and directory in other branches
Mailbox connection code.
Connects to mailboxes and perform import operations.
File
mailhandler.retrieve.incView source
<?php
/**
* @file
* Mailbox connection code.
*
* Connects to mailboxes and perform import operations.
*/
/**
* Returns the first part with the specified mime_type
*
* USAGE EXAMPLES - from php manual: imap_fetch_structure() comments
* $data = get_part($stream, $msg_number, "TEXT/PLAIN"); // get plain text
* $data = get_part($stream, $msg_number, "TEXT/HTML"); // get HTML text
*/
function mailhandler_get_part($stream, $msg_number, $mime_type, $structure = false, $part_number = false) {
if (!$structure) {
$structure = imap_fetchstructure($stream, $msg_number, FT_UID);
}
if ($structure) {
$encoding = variable_get('mailhandler_default_encoding', 'UTF-8');
foreach ($structure->parameters as $parameter) {
if (strtoupper($parameter->attribute) == 'CHARSET') {
$encoding = $parameter->value;
}
}
if ($mime_type == mailhandler_get_mime_type($structure)) {
if (!$part_number) {
$part_number = '1';
}
$text = imap_fetchbody($stream, $msg_number, $part_number, FT_UID);
if ($structure->encoding == ENCBASE64) {
return drupal_convert_to_utf8(imap_base64($text), $encoding);
}
else {
if ($structure->encoding == ENCQUOTEDPRINTABLE) {
return drupal_convert_to_utf8(quoted_printable_decode($text), $encoding);
}
else {
return drupal_convert_to_utf8($text, $encoding);
}
}
}
if ($structure->type == TYPEMULTIPART) {
/* multipart */
$prefix = '';
while (list($index, $sub_structure) = each($structure->parts)) {
if ($part_number) {
$prefix = $part_number . '.';
}
$data = mailhandler_get_part($stream, $msg_number, $mime_type, $sub_structure, $prefix . ($index + 1));
if ($data) {
return $data;
}
}
}
}
return false;
}
/**
* Returns an array of parts as file objects
*
* @param
* @param $structure
* A message structure, usually used to recurse into specific parts
* @param $max_depth
* Maximum Depth to recurse into parts.
* @param $depth
* The current recursion depth.
* @param $part_number
* A message part number to track position in a message during recursion.
* @return
* An array of file objects.
*/
function mailhandler_get_parts($stream, $msg_number, $max_depth = 10, $depth = 0, $structure = FALSE, $part_number = FALSE) {
$parts = array();
// Load Structure.
if (!$structure && !($structure = imap_fetchstructure($stream, $msg_number, FT_UID))) {
mailhandler_watchdog_record('Could not fetch structure for message number %msg_number', array(
'%msg_number' => $msg_number,
), WATCHDOG_ERROR);
return $parts;
}
// Recurse into multipart messages.
if ($structure->type == TYPEMULTIPART) {
// Restrict recursion depth.
if ($depth >= $max_depth) {
mailhandler_watchdog_record('Maximum recursion depths met in mailhander_get_structure_part for message number %msg_number.', array(
'%msg_number' => $msg_number,
), WATCHDOG_ERROR);
return $parts;
}
$prefix = '';
foreach ($structure->parts as $index => $sub_structure) {
// If a part number was passed in and we are a multitype message, prefix the
// the part number for the recursive call to match the imap4 dot seperated part indexing.
if ($part_number) {
$prefix = $part_number . '.';
}
$sub_parts = mailhandler_get_parts($stream, $msg_number, $max_depth, $depth + 1, $sub_structure, $prefix . ($index + 1));
$parts = array_merge($parts, $sub_parts);
}
return $parts;
}
// Per Part Parsing.
// Initalize file object like part structure.
$part = new StdClass();
$part->attributes = array();
$part->filename = 'unnamed_attachment';
if (!($part->filemime = mailhandler_get_mime_type($structure))) {
mailhandler_watchdog_record('Could not fetch mime type for message part. Defaulting to application/octet-stream.', array(), WATCHDOG_NOTICE);
$part->filemime = 'application/octet-stream';
}
if ($structure->ifparameters) {
foreach ($structure->parameters as $parameter) {
switch (strtoupper($parameter->attribute)) {
case 'NAME':
case 'FILENAME':
$part->filename = $parameter->value;
break;
default:
// put every thing else in the attributes array;
$part->attributes[$parameter->attribute] = $parameter->value;
}
}
}
// Handle Content-Disposition parameters for non-text types.
if ($structure->type != TYPETEXT && $structure->ifdparameters) {
foreach ($structure->dparameters as $parameter) {
switch (strtoupper($parameter->attribute)) {
case 'NAME':
case 'FILENAME':
$part->filename = $parameter->value;
break;
// put every thing else in the attributes array;
default:
$part->attributes[$parameter->attribute] = $parameter->value;
}
}
}
// Retrieve part and convert MIME encoding to UTF-8
if (!($part->data = imap_fetchbody($stream, $msg_number, $part_number, FT_UID))) {
mailhandler_watchdog_record('Mail has No Data!!', array(), WATCHDOG_ERROR);
return $parts;
}
// Decode as necessary.
if ($structure->encoding == ENCBASE64) {
$part->data = imap_base64($part->data);
}
elseif ($structure->encoding == ENCQUOTEDPRINTABLE) {
$part->data = quoted_printable_decode($part->data);
}
elseif ($structure->type == TYPETEXT) {
$part->data = imap_utf8($part->data);
}
//always return an array to satisfy array_merge in recursion catch, and array return value.
$parts[] = $part;
return $parts;
}
/**
* Retrieve MIME type of the message structure.
*/
function mailhandler_get_mime_type(&$structure) {
static $primary_mime_type = array(
'TEXT',
'MULTIPART',
'MESSAGE',
'APPLICATION',
'AUDIO',
'IMAGE',
'VIDEO',
'OTHER',
);
$type_id = (int) $structure->type;
if (isset($primary_mime_type[$type_id]) && !empty($structure->subtype)) {
return $primary_mime_type[$type_id] . '/' . $structure->subtype;
}
return 'TEXT/PLAIN';
}
function mailhandler_commands_parse($body, $sep) {
$commands = array();
// Collect the commands and locate signature
$lines = explode("\n", $body);
for ($i = 0; $i < count($lines); $i++) {
$line = trim($lines[$i]);
$words = explode(' ', $line);
// Look for a command line. if not present, note which line number is the boundary
if (substr($words[0], -1) == ':' && !isset($endcommands)) {
// Looks like a name: value pair
$commands[$i] = explode(': ', $line, 2);
}
else {
if (!isset($endcommands)) {
$endcommands = $i;
}
}
// Stop when we encounter the sig. we'll discard all remaining text.
$start = substr($lines[$i], 0, strlen($sep) + 3);
if ($sep && strstr($start, $sep)) {
// mail clients sometimes prefix replies with ' >'
break;
}
}
return array(
'commands' => $commands,
'lines' => $lines,
'i' => $i,
'endcommands' => $endcommands,
);
}
/**
* Defines and executes message authentication methods
*
* Message authentication methods can be defined using mailhandler_authenticate_info() which can
* take one of two $op's:
* - info, which is used to define an authentication plugin
* - execute, which is used to execute an authentication plugin
*
* @param $op
* String info or execute
* @param $name
* String identifier for authentication method
* @param $args
* Array of arguments to pass in to the authentication method callback
*/
function mailhandler_mailhandler_authenticate($op, $name = NULL, $args = array()) {
$methods = array();
switch ($op) {
case 'info':
foreach (module_list() as $module) {
if (module_hook($module, 'mailhandler_authenticate_info')) {
$function = $module . '_mailhandler_authenticate_info';
$methods[] = $function();
}
}
return $methods;
case 'execute':
foreach (module_list() as $module) {
if (module_hook($module, 'mailhandler_authenticate_info')) {
$function = $module . '_mailhandler_authenticate_info';
$methods = $function('info');
// TODO - May not be found, like if providing module is disabled. Must fail gracefully
foreach ($methods as $key => $method) {
if ($name == $key) {
if ($method['extension'] && $method['basename']) {
module_load_include($method['extension'], $method['module'], $method['basename']);
return call_user_func_array($method['callback'], $args);
}
else {
drupal_load('module', $method['module']);
return call_user_func_array($method['callback'], $args);
}
break 2;
}
}
}
}
// Return FALSE if callback is not found.
return FALSE;
break;
}
}
function mailhandler_batch_finished($success, $results, $operations) {
if ($success) {
$message = format_plural(count($results), '1 message retrieved for %mailbox.', '@count messages retrieved for %mailbox.', array(
'%mailbox' => $results[0]['mailbox']['mail'],
));
drupal_set_message($message);
}
if (!empty($results)) {
// Return the results to any asking modules
foreach (module_list() as $name) {
if (module_hook($name, 'mailhandler_batch_results')) {
$function = $name . '_mailhandler_batch_results';
if (!($messages = $function($results))) {
// Exit if a module has handled the submitted data.
break;
}
}
}
}
}
/**
* Obtain the number of unread messages for an imap stream
*
* @param $result
* IMAP stream - as opened by imap_open
* @return
* Array, values contain message numbers
*/
function mailhandler_get_unread_messages($result) {
$unread_messages = array();
$number_of_messages = imap_num_msg($result);
for ($i = 1; $i <= $number_of_messages; $i++) {
$header = imap_headerinfo($result, $i);
// only process new messages
if ($header->Unseen != 'U' && $header->Recent != 'N') {
continue;
}
$unread_messages[] = imap_uid($result, $i);
}
return $unread_messages;
}
/**
* Retrieve individual messages from an IMAP result
*
* @param $result
* IMAP stream
* @param $mailbox
* Array of mailbox configuration
* @param $i
* Int message number
* @param $context
* Array used by batch API
* @return unknown_type
*/
function mailhandler_retrieve_message($result, $mailbox, $i, &$context) {
// Required for batch API.
if (!$result) {
$result = mailhandler_open_mailbox($mailbox);
}
$header = imap_headerinfo($result, imap_msgno($result, $i));
// Initialize the subject in case it's missing.
if (!isset($header->subject)) {
$header->subject = '';
}
$mime = explode(',', $mailbox['mime']);
// Get the first text part - this will be the node body
$origbody = mailhandler_get_part($result, $i, $mime[0]);
// If we didn't get a body from our first attempt, try the alternate format (HTML or PLAIN)
if (!$origbody) {
$origbody = mailhandler_get_part($result, $i, $mime[1]);
}
// Parse MIME parts, so all mailhandler modules have access to
// the full array of mime parts without having to process the email.
$mimeparts = mailhandler_get_parts($result, $i);
// Is this an empty message with no body and no mimeparts?
if (!$origbody && !$mimeparts) {
watchdog('mailhandler', 'no body');
// @TODO: Log that we got an empty email?
// TODO: We should not just close here, need to keep open in case of cron/auto
imap_close($result);
return;
}
// Don't delete while we're only getting new messages
if ($mailbox['delete_after_read']) {
imap_delete($result, $i, FT_UID);
}
$message = array(
'header' => $header,
'origbody' => $origbody,
'mimeparts' => $mimeparts,
'mailbox' => $mailbox,
);
// If using batch API, must close imap stream. Cron uses single stream.
if (!empty($context) && array_key_exists('sandbox', $context)) {
$context['results'][] = $message;
imap_close($result, CL_EXPUNGE);
}
else {
return $message;
}
}
/**
* Connect to mailbox and run message retrieval
*
* @param $mailbox
* Array of mailbox configuration
* @param $mode
* String, the retrieval mode, via the ui/batch system, or automated/cron/queue
* @param $limit
* Int - the maximim number of messages to fetch on retrieval, only for 'auto' mode
*/
function mailhandler_retrieve($mailbox, $mode, $limit = 0) {
// This is cast as string in hook_menu, otherwise the url argument would get used.
$limit = (int) $limit;
if ($result = mailhandler_open_mailbox($mailbox)) {
$new = mailhandler_get_unread_messages($result);
}
switch ($mode) {
case 'ui':
if ($result) {
// Batch does not support using a single stream because it makes multiple page calls.
// The stream will be opened within mailhandler_retrieve_message
imap_close($result, CL_EXPUNGE);
$result = 0;
if (!empty($new)) {
foreach ($new as $message) {
$message_number = !$mailbox['imap'] ? 1 : $message;
$operations[] = array(
'mailhandler_retrieve_message',
array(
$result,
$mailbox,
$message_number,
),
);
}
$batch = array(
'title' => 'Mailhandler retrieve',
'operations' => $operations,
'finished' => 'mailhandler_batch_finished',
'init_message' => format_plural(count($new), 'Preparing to retrieve 1 message...', 'Preparing to retrieve @count messages...'),
'progress_message' => t('Retrieving message @current of @total.'),
'file' => drupal_get_path('module', 'mailhandler') . '/mailhandler.retrieve.inc',
);
batch_set($batch);
// Make 'progressive' mode work. Hack due to bug http://drupal.org/node/638712
$batch =& batch_get();
$batch['progressive'] = FALSE;
batch_process('admin/content/mailhandler');
}
else {
drupal_set_message(t('There are no messages to retrieve for %mail.', array(
'%mail' => $mailbox['mail'],
)));
}
}
else {
drupal_set_message(t('Unable to connect to %mail.', array(
'%mail' => $mailbox['mail'],
)));
}
drupal_goto('admin/content/mailhandler');
break;
case 'auto':
if ($result) {
if (!empty($new)) {
$messages = array();
$retrieved = 0;
while ($new && (!$limit || $retrieved < $limit)) {
$messages[] = mailhandler_retrieve_message($result, $mailbox, array_shift($new), $context = array());
$retrieved++;
}
imap_close($result, CL_EXPUNGE);
return $messages;
}
}
else {
mailhandler_watchdog_record('Unable to connect to %mail', array(
'%mail' => $mailbox['mail'],
), WATCHDOG_WARNING);
}
break;
}
}
/**
* @ingroup mailhandler_deprecated
* @{
*/
/**
* (DEPRECATED) Establish IMAP stream connection to specified mailbox.
*/
function mailhandler_open_mailbox($mailbox) {
mailhandler_watchdog_deprecated();
return mailhandler_mailbox_stream_open($mailbox);
}
/**
* (DEPRECATED) Switch from original user to mail submision user and back.
*/
function mailhandler_switch_user($uid = NULL) {
mailhandler_watchdog_deprecated();
return mailhandler_user_switch($uid);
}
Functions
Name![]() |
Description |
---|---|
mailhandler_batch_finished | |
mailhandler_commands_parse | |
mailhandler_get_mime_type | Retrieve MIME type of the message structure. |
mailhandler_get_part | Returns the first part with the specified mime_type |
mailhandler_get_parts | Returns an array of parts as file objects |
mailhandler_get_unread_messages | Obtain the number of unread messages for an imap stream |
mailhandler_mailhandler_authenticate | Defines and executes message authentication methods |
mailhandler_open_mailbox | (DEPRECATED) Establish IMAP stream connection to specified mailbox. |
mailhandler_retrieve | Connect to mailbox and run message retrieval |
mailhandler_retrieve_message | Retrieve individual messages from an IMAP result |
mailhandler_switch_user | (DEPRECATED) Switch from original user to mail submision user and back. |