TRUE));
// Process mailboxes.
foreach ($mailboxes as $mid => $mailbox) {
mailhandler_node_process_mailbox((array)$mailbox, 'auto');
}
}
/**
* Implementation of hook_perm().
*/
function mailhandler_perm() {
return array('administer mailhandler');
}
/**
* Implementation of hook_menu().
*/
function mailhandler_menu() {
$items = array();
$items['admin/content/mailhandler'] = array(
'title' => 'Mailhandler Mailboxes',
'description' => 'Manage mailboxes and retrieve messages.',
'page callback' => 'mailhandler_list_mailboxes',
'access arguments' => array('administer mailhandler'),
'file' => 'mailhandler.admin.inc',
);
$items['admin/content/mailhandler/list'] = array(
'title' => 'List',
'description' => 'Manage mailboxes and retrieve messages.',
'type' => MENU_DEFAULT_LOCAL_TASK,
'access arguments' => array('administer mailhandler'),
'weight' => -10,
);
$items['admin/content/mailhandler/add'] = array(
'title' => 'Add mailbox',
'page callback' => 'drupal_get_form',
'page arguments' => array('mailhandler_add_edit_mailbox', NULL),
'access arguments' => array('administer mailhandler'),
'type' => MENU_LOCAL_TASK,
'file' => 'mailhandler.admin.inc',
);
$items['admin/content/mailhandler/clone/%mailhandler_mailbox'] = array(
'title' => 'Add mailbox',
'page callback' => 'drupal_get_form',
'page arguments' => array('mailhandler_add_edit_mailbox', 4, TRUE),
'access arguments' => array('administer mailhandler'),
'type' => MENU_CALLBACK,
'file' => 'mailhandler.admin.inc',
);
$items['admin/content/mailhandler/retrieve/%mailhandler_mailbox'] = array(
'title' => 'Retrieve',
'page callback' => 'mailhandler_node_process_mailbox',
'page arguments' => array(4, 'ui'),
'access arguments' => array('administer mailhandler'),
'type' => MENU_CALLBACK,
'file' => 'mailhandler.retrieve.inc',
);
$items['admin/content/mailhandler/edit/%mailhandler_mailbox'] = array(
'title' => 'Edit mailbox',
'page callback' => 'drupal_get_form',
'page arguments' => array('mailhandler_add_edit_mailbox', 4),
'access arguments' => array('administer mailhandler'),
'type' => MENU_CALLBACK,
'file' => 'mailhandler.admin.inc',
);
$items['admin/content/mailhandler/delete/%mailhandler_mailbox'] = array(
'title' => 'Delete mailbox',
'page callback' => 'drupal_get_form',
'page arguments' => array('mailhandler_admin_delete_confirm', 4),
'access arguments' => array('administer mailhandler'),
'type' => MENU_CALLBACK,
'file' => 'mailhandler.admin.inc',
);
$items['admin/settings/mailhandler'] = array(
'title' => 'Mailhandler Settings',
'description' => t('Set the default content type for incoming messages and set the cron limit.'),
'page callback' => 'drupal_get_form',
'page arguments' => array('mailhandler_admin_settings'),
'access arguments' => array('administer mailhandler'),
'file' => 'mailhandler.admin.inc',
);
return $items;
}
/**
* Implementation of hook_help().
*/
function mailhandler_help($path = 'admin/help#mailhandler', $arg) {
$output = '';
// Gather examples of useful commands, and build a definition list with them:
$commands[] = array('command' => 'taxonomy: [term1, term2]',
'description' => t('Use this to add the terms term1 and term2 to the node.
Both of the terms should already exist. In case they do not exist already, they will be quietly ommitted'));
$commands[] = array('command' => 'taxonomy[v]: [term1, term2]',
'description' => t('Similar to the above: adds the terms term1 and term2 to the node, but uses the vocabulary with the vocabulary id v. For example taxonomy[3] will chose only terms from the vocabulary which id is 3.
In case some of the terms do not exist already, the behavior will depend on whether the vocabulary is a free tagging vocabulary or not. If it is a free tagging vocabulary, the term will be added, otherwise, it will be quietly ommitted'));
$commands_list = '
'. t('The mailhandler module allows registered users to create or edit nodes and comments via e-mail. Users may post taxonomy terms, teasers, and other post attributes using the mail commands capability. This module is useful because e-mail is the preferred method of communication by community members.') .'
'; $output .= ''. t('The mailhandler module requires the use of a custom mailbox. Administrators can add mailboxes that should be customized to meet the needs of a mailing list. This mailbox will then be checked on every cron job. Administrators may also initiate a manual retrieval of messages.') .'
'; $output .= ''. t('This is particularly useful when you want multiple sets of default commands. For example , if you want to authenticate based on a non-standard mail header like Sender: which is useful for accepting submissions from a listserv. Authentication is usually based on the From: e-mail address. Administrators can edit the individual mailboxes when they administer mailhandler.') .'
'; $output .= t('You can
'. t('For more information please read the configuration and customization handbook Mailhandler page.', array('@mailhandler' => url('http://www.drupal.org/handbook/modules/mailhandler', array('absolute' => TRUE)))) .'
'; return $output; case 'admin/content/mailhandler': return t('The mailhandler module allows registered users to create or edit nodes and comments via email. Authentication is usually based on the From: email address. There is also an email filter that can be used to prettify incoming email. Users may post taxonomy terms, teasers, and other node parameters using the Command capability.'); case 'admin/content/mailhandler/add': return t('Add a mailbox whose mail you wish to import into Drupal. Can be IMAP, POP3, or local folder.'); case 'admin/content/mailhandler/edit/%': return t('Edit the mailbox whose mail you wish to import into Drupal. Can be IMAP, POP3, or local folder.'); case 'admin/settings/mailhandler': return t('The mailhandler module allows registered users to create or edit nodes and comments via e-mail.'); } } /** * Mailhandler general purpose functions. * * The functions included here are helpers for the module operation. */ /** * Log a Mailhandler message. * * This is a wrapper call to watchdog, to avoid selective events recording from * the Mailhandler configuration. * * @param $message * The message to store in the log. See t() for documentation * on how $message and $variables interact. Keep $message * translatable by not concatenating dynamic values into it! * @param $variables * Array of variables to replace in the message on display or * NULL if message is already translated or not possible to * translate. * @param $severity * The severity of the message, as per RFC 3164. Possible values are * WATCHDOG_ERROR, WATCHDOG_WARNING, etc. * @param $link * A link to associate with the message. * * @see watchdog_severity_levels() * @see watchdog() */ function mailhandler_watchdog_record($message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) { if ($severity <= variable_get('mailhandler_watchdog_level', 6)) { watchdog('mailhandler', $message, $variables, $severity, $link); } } /** * Mailhandler Mailbox database operation. * * The CRUD implemented here mimics the way CRUD operations are being done in * Drupal 7, to make both versions similar and easy to maintain. */ /** * Load a mailbox array from database. * * Return a mailbox information based on the ID passed as parameter. * * @param $mid * Int mailbox ID to load * @param $conditions * An associative array of conditions on the {mailhandler} table, where the * keys are the database fields and the values are the values those fields * must have. * @param $reset * Whether to reset the internal mailhandler_mailbox_load cache. Unimplemented * to make module versions easy to maintain. * @return * Array of mailbox configuration */ function mailhandler_mailbox_load($mid, $conditions = array(), $reset = FALSE) { $mids = (isset($mid) ? array($mid) : array()); $mailbox = mailhandler_mailbox_load_multiple($mids, $conditions, $reset); return $mailbox ? (array) reset($mailbox) : FALSE; } /** * Load mailboxes from the database. * * This function should be used whenever you need to load more than one mailbox * from the database. Mailboxes are loaded into memory and will not require * database access if loaded again during the same page request. * * @param $mids * An array of mailboxes IDs. If specified, the conditions will be applied to * this group of mailboxes. * @param $conditions * An associative array of conditions on the {mailhandler} table, where the * keys are the database fields and the values are the values those fields * must have. This filter will return only mailboxes matching all the values. * @param $reset * Whether to reset the internal mailhandler_mailbox_load cache. * @return * An array of mailboxes indexed by mid */ function mailhandler_mailbox_load_multiple($mids = array(), $conditions = array(), $reset = FALSE) { $mailboxes = array(); // Use these variables to compose the query based on $conditions array. $schema = drupal_get_schema('mailhandler'); $fields = drupal_schema_fields_sql('mailhandler'); $conds = $params = array(); // Select from this group of mailboxes if defined. if (count($mids)) { $conds[] = 'mid IN (' . db_placeholders($mids, 'int') . ')'; foreach ($mids as $mid) { $params[] = intval($mid); } } // Apply the conditions filter. foreach ($conditions as $key => $value) { if (in_array($key, $fields)) { $conds[] = $key . " = " . db_type_placeholder($schema['fields'][$key]['type']); $params[] = $value; } } // Build and execute the query $cond = count($conds) ? ' WHERE ' . implode(' AND ', $conds) : ''; $result = db_query('SELECT ' . implode(', ', $fields) . ' FROM {mailhandler}' . $cond, $params); // Populate the return array. $search = array(); while ($mailbox = db_fetch_object($result)) { $mailboxes[$mailbox->mid] = $mailbox; } return $mailboxes; } /** * Validate a Mailhandler Mailbox. * * Validate the mailbox and set form errors. Even if this function is not used * in a form (e.g. validating mailboxes created programatically), the function * will still set form errors. These errors can be later recalled using the * function form_get_errors(). * *
* mailhandler_mailbox_validate($mailbox);
* if ($errors = form_get_errors()) {
* do_my_error_handling($errors);
* }
* else {
* mailhandler_mailbox_save($mailbox);
* }
*
*
* @param $mailbox
* A mailbox settings array.
* @param $form
* The $form where mailbox is being edited.
* @param $form_state
* The $form_state values of the form being validated.
*/
function mailhandler_mailbox_validate($mailbox, $form = array(), &$form_state = array()) {
// Check for valid email address
if ($error = user_validate_mail($mailbox['mail'])) {
form_set_error('mail', $error);
}
if ($form_state['values']['mailto'] && ($error = user_validate_mail($mailbox['mailto']))) {
form_set_error('mailto', $error);
}
// Test POP/IMAP settings, and store result
$mailbox_appears_ok = TRUE;
// Assume external mailbox.
if ($mailbox['domain'] && $mailbox['port'] && !is_numeric($mailbox['port'])) {
form_set_error('port', t('Mailbox port must be an integer.'));
$mailbox_appears_ok = FALSE;
}
// Assume local folder.
if (!$mailbox['domain'] && !$mailbox['port'] && $mailbox['folder']) {
// check read and write permission
if (!is_readable($mailbox['folder']) || !is_writable($mailbox['folder'])) {
form_set_error('port', t('The local folder has to be readable and writable by owner of the webserver process, e.g. nobody.'));
$mailbox_appears_ok = FALSE;
}
}
// If POP3 mailbox is chosen, messages must be deleted after processing.
if ($mailbox['imap'] == 0 && $mailbox['delete_after_read'] == 0) {
form_set_error('delete_after_read', t('You must check off "Delete messages after they are processed" when using a POP3 mailbox.'));
}
// Test POP/IMAP settings are valid in all cases, and test connection if requested
if ($mailbox_appears_ok && $form_state['clicked_button']['#value'] == t('Test connection')) {
// Call the test function
mailhandler_test_mailbox($form_state);
// We don't want to save, so we set an 'invisible error' that means the form is not submitted
form_set_error('mailhandler');
}
}
/**
* Save a Mailhandler Mailbox in the database.
*
* When $mailbox has 'mid' attribute defined, the database entry with this ID
* will be updated, otherwise a new mailbox will be created.
*
* @param $mailbox
* A mailbox settings array.
*/
function mailhandler_mailbox_save(&$mailbox) {
// Determine this is an update or a new mailbox.
$update = (isset($mailbox['mid']) && $mailbox['mid']) ? array('mid') : array();
// Set default values for missing mailbox attributes.
$mailbox += mailhandler_mailbox_defaults();
// Save entry into database.
drupal_write_record('mailhandler', $mailbox, $update);
}
/**
* Delete a Mailhandler Mailbox from the database.
*
* When $mailbox has 'mid' attribute defined, the database entry with this ID
* will be updated, otherwise a new mailbox will be created.
*
* @param $mid
* mailbox ID to be deleted.
*/
function mailhandler_mailbox_delete($mid) {
db_query("DELETE FROM {mailhandler} WHERE mid = %d", $mid);
}
/**
* Return mailhandler mailbox default attributes.
*
* @return
* Associative array with mailhandler mailbox default attributes.
*/
function mailhandler_mailbox_defaults() {
return array(
'mail' => '',
'mailto' => '',
'folder' => 'INBOX',
'imap' => 0,
'domain' => '',
'port' => '',
'name' => '',
'pass' => '',
'extraimap' => '',
'mime' => 'TEXT/HTML,TEXT/PLAIN',
'security' => 0,
'replies' => 1,
'fromheader' => '',
'commands' => '',
'sigseparator' => '',
'delete_after_read' => 1,
'enabled' => 1,
'format' => variable_get('filter_default_format', 1),
'authentication' => 'mailhandler_default',
);
}
/**
* Fetch data for a specific mailbox from the database. (DEPRECATED)
*
* To be removed once it is not in use by other module. Currently this is just a
* wrapper around mailhandler_mailbox_load().
*/
function mailhandler_get_mailbox($mid) {
return mailhandler_mailbox_load($mid);
}
/**
* Mailhandler mailbox Processing functions.
*/
/**
* Return a default content type if the user has not chosen a specific type on the settings page
* In order of priority, return blog, story, page
* This assumes that one of these basic types is in use on a site (page and story are active by default)
* A user can choose other content types via the settings page as this exposes all available types
*/
function mailhandler_default_type() {
// Get the current default setting, if defined
$default_type = variable_get('mailhandler_default_type', NULL);
// Find out what types are available
$available_types = node_get_types('names');
// Check the default type is still available (it could have been deleted)
if ($default_type && array_key_exists($default_type, $available_types)) {
return $default_type;
}
// If we get here then either no default is set, or the default type is no longer available
// Search for the array key (the machine readable name) for blog, story and page basic types
if (array_key_exists('blog', $available_types)) {
$default_type = 'blog';
}
else if (array_key_exists('story', $available_types)) {
$default_type = 'story';
}
else if (array_key_exists('page', $available_types)) {
$default_type = 'page';
}
else {
// If basic types not found then return the first item from the array as an alternative default
$default_type = key($available_types);
}
return $default_type;
}
/**
* Run message retrieval and node processing on a mailbox - is a wrapper around mailhandler_retrieve
*
* Batch API smashes the stack, so this function is called twice when fetching
* messages via batch in order to return the results from the batch.
*
* @param $mailbox
* Array of mailbox configuration
* @param $mode
* String ui or auto (ie cron) processing
* @param $limit
* Int maximum number of messages to retrieve
* @param $messages
* Array of retrieved messages
*/
function mailhandler_node_process_mailbox($mailbox = FALSE, $mode = FALSE, $limit = FALSE, $messages = array()) {
// Set default maximun message limit according to general settings.
$limit = isset($limit) ? $limit : variable_get('mailhandler_max_retrieval', 0);
if (empty($messages)) {
if ($mode == 'ui') {
mailhandler_retrieve($mailbox, $mode, $limit);
}
else {
if ($messages = mailhandler_retrieve($mailbox, $mode, $limit)) {
foreach ($messages as $message) {
mailhandler_node_process_message($message['header'], $message['origbody'], $message['mailbox'], $message['mimeparts']);
}
}
}
}
else {
// See mailhandler_mailhandler_batch_results. This hook is invoked
// and mailhandler_process_mailbox is called a second time in order
// to obtain the messages that were retrieved and continue processing.
// This is necessary because we cannot obtain the results back from
// batch processing directly.
foreach ($messages as $message) {
mailhandler_node_process_message($message['header'], $message['origbody'], $message['mailbox'], $message['mimeparts']);
}
}
}
/**
* Implementation of hook_mailhandler_batch_results()
*
* See mailhandler_process_mailbox()
*
*/
function mailhandler_mailhandler_batch_results($results) {
mailhandler_node_process_mailbox(FALSE, FALSE, FALSE, $results);
// Give the results back for any other modules to use
return $results;
}
// TODO: add arguments
function mailhandler_node_process_message($header, $origbody, $mailbox, $mimeparts) {
mailhandler_switch_user();
// we must process before authenticating because the password may be in Commands
$node = mailhandler_node_prepare_message($header, $origbody, $mailbox);
// Authenticate the message
if (!$node = mailhandler_mailhandler_authenticate('execute', $mailbox['authentication'], array($node, $header, $origbody, $mailbox))) {
watchdog('mailhandler', 'Message failed authentication', array(), WATCHDOG_ERROR);
return FALSE;
}
// Put $mimeparts on the node
$node->mimeparts = $mimeparts;
// we need to change the current user
// this has to be done here to allow modules
// to create users
mailhandler_switch_user($node->uid);
// modules may override node elements before submitting. they do so by returning the node.
foreach (module_list() as $name) {
if (module_hook($name, 'mailhandler')) {
$function = $name .'_mailhandler';
if (!($node = $function($node, $result, $i, $header, $mailbox))) {
// Exit if a module has handled the submitted data.
break;
}
}
}
if ($node) {
if ($node->type == 'comment') {
$nid = mailhandler_comment_submit($node, $header, $mailbox, $origbody);
$type = 'comment';
}
else {
$nid = mailhandler_node_submit($node, $header, $mailbox, $origbody);
$type = 'node';
}
}
// Invoke a second hook for modules to operate on the newly created/edited node/comment.
foreach (module_list() as $name) {
if (module_hook($name, 'mailhandler_post_save')) {
$function = $name .'_mailhandler_post_save';
// Pass in the $nid (which could be a $cid, depending on $node->type)
$function($nid, $type);
}
}
// switch back to original user
mailhandler_switch_user();
// Put something in the results array for the counter in the batch finished callback
$context['results'][] = $mailbox['mail'];
mailhandler_switch_user();
}
/**
* Append default commands. Separate commands from body. Strip signature.
* Return a node object.
*/
function mailhandler_node_prepare_message($header, $body, $mailbox) {
// Initialise a node object
$node = new stdClass();
$node->pass = NULL;
// Initialize parameters
$sep = $mailbox['sigseparator'];
// Copy any name/value pairs from In-Reply-To or References e-mail headers to $node. Useful for maintaining threading info.
if (!empty($header->references)) {
// we want the final element in references header, watching out for white space
$threading = substr(strrchr($header->references, '<'), 0);
}
else if (!empty($header->in_reply_to)) {
$threading = str_replace(strstr($header->in_reply_to, '>'), '>', $header->in_reply_to); // Some MUAs send more info in that header.
}
if (isset($threading) && $threading = rtrim(ltrim($threading, '<'), '>')) { //strip angle brackets
if ($threading) $node->threading = $threading;
parse_str($threading, $tmp);
if ($tmp['host']) {
$tmp['host'] = ltrim($tmp['host'], '@'); // strip unnecessary @ from 'host' element
}
foreach ($tmp as $key => $value) {
$node->$key = $value;
}
}
// Prepend the default commands for this mailbox
if ($mailbox['commands']) {
$body = trim($mailbox['commands']) ."\n". $body;
}
if ($commands = mailhandler_commands_parse($body, $sep)) {
// The node type must be set first in order to properly initialize the node
foreach ($commands['commands'] as $command) {
if ($command[0] == 'type') {
$node->type = $command[1];
}
}
}
// Set a default type if none provided
if (!isset($node->type)) $node->type = mailhandler_default_type();
// Apply defaults to the $node object, and allow modules to add default values
require_once(drupal_get_path('module', 'node') . '/node.pages.inc');
node_object_prepare($node);
// If cron is called by anon then node_object_prepare sets uid to 0, but otherwise global $user.
// Manually set this to 0 since that's what the rest of the code expects.
$node->uid = 0;
// In order to fall back to the permission system for comment status, the status property must
// be unset if type is comment. It will get set by explicit commands, and if not, by
// comment_save itself.
if($node->type == 'comment') {
unset($node->status);
}
// Execute the commands
if (!empty($commands['commands'])) {
mailhandler_node_process_message_commands($node, $commands['commands']);
}
// Isolate the body from the commands and the sig
$tmp = array_slice($commands['lines'], $commands['endcommands'], $commands['i'] - $commands['endcommands']);
// flatten and assign the body to node object. note that filter() is called within node_save() [tested with blog post]
$type = node_get_types('type', $node->type);
if ($type->has_body) {
$node->body = implode("\n", $tmp);
}
if (empty($node->teaser)) $node->teaser = node_teaser($node->body);
// decode encoded subject line
$subjectarr = imap_mime_header_decode($header->subject);
if (empty($subjectarr)) {
$node->title = truncate_utf8(trim(decode_entities(strip_tags(check_markup($node->body)))), 29, TRUE);
}
else {
for ($i = 0; $i < count($subjectarr); $i++) {
if ($subjectarr[$i]->charset != 'default')
$node->title .= drupal_convert_to_utf8($subjectarr[$i]->text, $subjectarr[$i]->charset);
else
$node->title .= $subjectarr[$i]->text;
}
}
$node->date = $node->changed = format_date($header->udate, 'custom', 'Y-m-d H:i:s O');
$node->format = $mailbox['format'];
// If an nid command was supplied, and type is not 'comment', append the revision number
if (isset($node->nid) && !empty($node->nid) && $node->type != 'comment') {
$vid = db_result(db_query('SELECT n.vid FROM {node} n WHERE n.nid = %d', $node->nid));
if ($vid) {
$node->revision = $node->vid = $vid;
}
}
return $node;
}
/**
* Create the comment.
*/
function mailhandler_comment_submit($node, $header, $mailbox, $origbody) {
global $user;
if (!$node->subject) $node->subject = $node->title;
// When submitting comments, 'comment' means actualy the comment's body, and not the comments status for a node.
// We need to reset the comment's body, so it doesn't colide with a default 'comment' command.
$node->comment = $node->body;
// comment_save will not fall back to permission system if we set the status explicitly
// See comment_save. += will not overwrite an existing array property.
if (property_exists($node, 'status')) {
// In comment module, status of 1 means unpublished, status of 0 means published.
$node->status == 1 ? $node->status = 0 : $node->status = 1;
}
// We want the comment to have the email time, not the current time
$node->timestamp = $node->created;
// comment_save gets an array
$edit = (array)$node;
// post the comment. if unable, send a failure email when so configured
$cid = comment_save($edit);
if (!$cid && $mailbox['replies']) {
// $fromaddress really refers to the mail header which is authoritative for authentication
list($fromaddress, $fromname) = mailhandler_get_fromaddress($header, $mailbox);
$error_text = t('Sorry, your comment experienced an error and was not posted. Possible reasons are that you have insufficient permission to post comments or the node is no longer open for comments.');
$params = array('body' => $origbody, 'error_messages' => array(), 'error_text' => $error_text, 'from' => $fromaddress, 'header' => $header, 'node' => $node);
drupal_mail('mailhandler', 'mailhandler_error_comment', $fromaddress, user_preferred_language($user), $params);
watchdog('mailhandler', 'Comment submission failure: %subject.', array('%subject' => $edit['subject']), WATCHDOG_ERROR);
}
return $cid;
}
/**
* Create the node.
*/
function mailhandler_node_submit($node, $header, $mailbox, $origbody) {
global $user;
list($fromaddress, $fromname) = mailhandler_get_fromaddress($header, $mailbox);
// Reset the static cache
form_set_error(NULL, '', TRUE);
node_validate($node);
if (!$error_messages = form_set_error()) {
// Prepare the node for save and allow modules make changes
$node = node_submit($node);
// Save the node
if (!empty($node->nid)) {
if (node_access('update', $node)) {
node_save($node);
watchdog('mailhandler', 'Updated %title by %from.', array('%title' => $node->title, '%from' => $fromaddress));
}
else {
$error_text = t('The e-mail address !from may not update !type items.', array('!from' => $fromaddress, '!type' => $node->type));
watchdog('mailhandler', 'Node submission failure: %from may not update %type items.', array('%from' => $fromaddress, '%type' => $node->type), WATCHDOG_WARNING);
}
}
else {
$account = user_load($node->uid);
if (node_access('create', $node, $account)) {
node_save($node);
watchdog('mailhandler', 'Added %title by %from.', array('%title' => $node->title, '%from' => $fromaddress));
}
else {
$error_text = t('The e-mail address !from may not create !type items.', array('!from' => $fromaddress, '!type' => $node->type));
watchdog('mailhandler', 'Node submission failure: %from may not create %type items.', array('%from' => $fromaddress, '%type' => $node->type), WATCHDOG_WARNING);
}
}
// Return the node is successfully saved
if (!isset($error_text)) {
return $node;
}
}
else {
$error_text = t('Your submission is invalid:');
watchdog('mailhandler', 'Node submission failure: validation error.', array(), WATCHDOG_WARNING);
}
if (isset($error_text)) {
if ($mailbox['replies']) {
$params = array('body' => $origbody, 'error_messages' => $error_messages, 'error_text' => $error_text, 'from' => $fromaddress, 'header' => $header, 'node' => $node);
drupal_mail('mailhandler', 'mailhandler_error_node', $fromaddress, user_preferred_language($user), $params);
}
}
// return FALSE if the node was not successfully saved
return FALSE;
}
/**
* Accept a taxonomy term name and replace with a tid. this belongs in taxonomy.module.
*/
function mailhandler_term_map(&$term, $index = array(), $vid = FALSE) {
// provide case insensitive and trimmed map so as to maximize likelihood of successful mapping
if ($vid) {
$and = 'AND vid =' . db_escape_string($vid);
}
$term = db_result(db_query("SELECT tid FROM {term_data} WHERE LOWER('%s') LIKE LOWER(name) $and", trim($term)));
}
function mailhandler_node_process_message_commands(&$node, $commands) {
if (module_exists('taxonomy')) {
$vocabs = taxonomy_get_vocabularies($node->type);
$node->taxonomy = array();
foreach ($commands as $data) {
// TODO: allow for nested arrays in commands ... Possibly trim() values after explode().
// If needed, turn this command value into an array
if (substr($data[1], 0, 1) == '[' && substr($data[1], -1, 1) == ']') {
$data[1] = rtrim(ltrim($data[1], '['), ']'); //strip brackets
$data[1] = explode(",", $data[1]);
// allow for key value pairs
foreach ($data[1] as $key => $value) {
$data_tmp = explode(":", $value, 2);
if (isset($data_tmp[1])) { // is it a key value pair?
// remove old, add as key value pair
unset($data[1][$key]);
$data_tmp[0] = trim($data_tmp[0]);
$data[1][$data_tmp[0]] = $data_tmp[1];
}
}
}
$data[0] = strtolower(str_replace(' ', '_', $data[0]));
// if needed, map term names into IDs. this should move to taxonomy_mailhandler()
if ($data[0] == 'taxonomy' && !is_numeric($data[1][0])) {
array_walk($data[1], 'mailhandler_term_map');
// Only add term if node type can use that term's vocab
$term = taxonomy_get_term($data[1][0]);
if (array_key_exists($term->vid, $vocabs)) {
$node->taxonomy = array_merge($node->taxonomy, $data[1]);
}
unset($data[0]);
}
else if (substr($data[0], 0, 9) == 'taxonomy[' && substr($data[0], -1, 1) == ']'){
// make sure a valid vid is passed in:
$vid = substr($data[0], 9, -1);
$vocabulary = taxonomy_vocabulary_load($vid);
// if the vocabulary is not activated for that node type, unset $data[0], so the command will be ommited from $node
// TODO: add an error message
if (!in_array($node->type, $vocabulary->nodes)) {
unset($data[0]);
}
else if (!$vocabulary->tags) {
array_walk($data[1], 'mailhandler_term_map', $vid);
$node->taxonomy = array_merge($node->taxonomy, $data[1]);
unset($data[0]);
}
else if ($vocabulary->tags) {
// for freetagging vocabularies, we just pass the list of terms
$node->taxonomy['tags'][$vid] = implode(',', $data[1]);
unset($data[0]); // unset, so it won't be included when populating the node object
}
}
if (!empty($data[0])) {
$node->$data[0] = $data[1];
}
}
}
else {
watchdog('mailhandler', 'Unable to process commands. Taxonomy module must be enabled for Mailhandler command processing to work.');
}
}
/**
* Implementation of hook_mail().
*/
function mailhandler_mail($key, &$message, $params) {
$variables = array(
'!body' => $params['body'],
'!from' => $params['from'],
'!site_name' => variable_get('site_name', 'Drupal'),
'!subject' => $params['header']->subject,
'!type' => $params['node']->type,
);
$message['subject'] = t('Email submission to !site_name failed - !subject', $variables);
$message['body'][] = $params['error_text'];
foreach ($params['error_messages'] as $key => $error) {
$message['body'][$key] = decode_entities(strip_tags($error));
}
$message['body'][] = t("You sent:\n\nFrom: !from\nSubject: !subject\nBody:\n!body", $variables);
}
/**
* Implementation of hook_mailhandler_authenticate_info()
*/
function mailhandler_mailhandler_authenticate_info() {
$info = array(
'mailhandler_default' => array(
'title' => 'Mailhandler Default',
'description' => 'Checks whether the sender matches a valid user in the database',
'callback' => 'mailhandler_authenticate_default',
'module' => 'mailhandler',
'extension' => NULL, // as in $type in module_load_include
'basename' => NULL, // as in $name in module_load_include
)
);
if (module_exists('tokenauth')) {
$info += array(
'mailhandler_tokenauth' => array(
'title' => 'Mailhandler Tokenauth',
'description' => 'Authenticate messages based on users token from Tokenauth module',
'callback' => 'mailhandler_authenticate_tokenauth',
'module' => 'mailhandler',
'extension' => NULL,
'basename' => NULL,
),
);
}
return $info;
}
/**
* Authenticate message based on sender's email address
* If the sender's email address matches an email address of a valid user, then assign
* that user's uid and name to the node object.
*
* @param $node
* Object a node object
* @param $header
* Object of message header information
* @param $origbody
* String message body text
* @param $mailbox
* Array of mailbox configuration
*
* @return object, the node object
*/
function mailhandler_authenticate_default($node, $header, $origbody, $mailbox) {
list($fromaddress, $fromname) = mailhandler_get_fromaddress($header, $mailbox);
if ($from_user = mailhandler_user_load($fromaddress, $node->pass, $mailbox)) {
$node->uid = $from_user->uid;
$node->name = $from_user->name;
}
// Try using mailalias email aliases
else if (function_exists('mailalias_user') && $from_user = mailhandler_user_load_alias($fromaddress, $node, $mailbox)) {
$node->uid = $from_user->uid;
$node->name = $from_user->name;
}
else {
// Authentication failed. Try as anonymous.
$node->uid = 0;
$node->name = $fromname;
}
return $node;
}
/**
* Authenticate message based on token from tokenauth module
* If the user's token is found somewhere in the "to" field, assign that user's uid and name
* to the node object. A rough search for the token somewhere in the "toaddress" is performed
* instead of an exact, ordered match in order to allow some freedom in the format of allowed
* "toaddress". For example, if using a catchall email address, the toaddress could be:
*
* f93ksj35dx@example.com - where f93ksj35dx is the user's token
* or alternatively:
* f93ksj35dx-foo@example.com - where f93ksj35dx is the user's token and foo is the name of an
* Organic Group to which the message should be assigned.
*
* A rough search allows for different approaches to use this single authentication method.
*
* @param $node
* Object a node object
* @param $header
* Object of message header information
* @param $origbody
* String message body text
* @param $mailbox
* Array of mailbox configuration
*
* @return object, the node object
*/
function mailhandler_authenticate_tokenauth($node, $header, $origbody, $mailbox) {
if (module_exists('tokenauth')) {
list($fromaddress, $fromname) = mailhandler_get_fromaddress($header, $mailbox);
// If user with given email address exists and their token is in the toaddress, allow.
if (($from_user = mailhandler_user_load($fromaddress, $node->pass, $mailbox))
&& strpos($header->to[0]->mailbox, tokenauth_get_token($from_user->uid)) !== FALSE) {
$node->uid = $from_user->uid;
$node->name = $from_user->name;
}
// Try using mailalias email aliases
else if (function_exists('mailalias_user') && ($from_user = mailhandler_user_load_alias($fromaddress, $node, $mailbox))
&& strpos($header->to[0]->mailbox, tokenauth_get_token($from_user->uid)) !== FALSE) {
$node->uid = $from_user->uid;
$node->name = $from_user->name;
}
else {
// If token authentication fails, try as anonymous.
$node->uid = 0;
$node->name = $fromname;
}
}
return $node;
}
/**
* Determine from address either using the mailbox setting or via the header information
*
* @param $header
* Object message header information
* @param $mailbox
* Array mailbox settings
* @return array
*/
function mailhandler_get_fromaddress($header, $mailbox) {
if (($fromheader = strtolower($mailbox['fromheader'])) && isset($header->$fromheader)) {
$from = $header->$fromheader;
}
else {
$from = $header->from;
}
return array($from[0]->mailbox .'@'. $from[0]->host, (isset($from[0]->personal)) ? $from[0]->personal : '');
}
/**
* Retrieve user information from his email address.
*/
function mailhandler_user_load($mail, $pass, $mailbox) {
if ($mailbox['security'] == 1) {
if (!$account = user_load(array('mail' => $mail, 'pass' => $pass))) {
watchdog('mailhandler', 'Wrong password used in message commands for %address', array('%address' => $mail), WATCHDOG_NOTICE);
}
return $account;
}
else {
return user_load(array('mail' => $mail));
}
}
/**
* Look up a user based on their mailalias addresses
*
* Helper function for mailhandler_authenticate_tokenauth()
*
* @param $fromaddress
* String from address
* @param $node
* Object node object
* @param $mailbox
* Array of mailhandler mailbox configuration
*
* @return Object user object or FALSE
*/
function mailhandler_user_load_alias($fromaddress, $node, $mailbox) {
$result = db_query("SELECT mail FROM {users} WHERE data LIKE '%%%s%%'", $fromaddress);
while ($alias = db_result($result)) {
if ($from_user = mailhandler_user_load($alias, $node->pass, $mailbox)) {
return $from_user;
break;
}
}
return FALSE;
}