Source for file getmail.php
Documentation is available at getmail.php
* modified for b2evolution 2.4.1 by Stephan Knauss. Contact me by PM in {@link http://forums.b2evolution.net/} (user stephankn)
* or send a mail to stephankn at users.sourceforge.net
* Uses MIME E-mail message parser classes written by Manuel Lemos: {@link http://www.phpclasses.org/browse/package/3169.html}
* This script can be called with a parameter "test" to specify what
* should be done and what level of debug output to generate:
* <li>level 0: default. Process everything, no debug output, no html (called by cronjob)</li>
* <li>level 1: Test only connection to server, do not process messages</li>
* <li>level 2: additionally process messages, but do not post</li>
* <li>level 3: do everything with extended verbosity</li>
* Only messages for "level 0" should get marked for translation (using T_()).
* Quam Plures - {@link http://quamplures.net/}
* Released under GNU GPL License - {@link http://quamplures.net/license.html}
* @author tblue246: Tilman Blumenbach
* @copyright (c) 2009 - 2011 by the Quam Plures developers - {@link http://quamplures.net/}
* @copyright (c)2003-2009 by Francois PLANQUE - {@link http://fplanque.net/}
* This file built upon code from original b2 - http://cafelog.com/
* @todo check different encodings. only tested with iso-8859-1
* @todo try more exotic email clients like mobile phones
* @todo tested and working with thunderbird (text, html, signed), yahoo mail (text, html), outlook webmail, K800i
* @todo Allow the user to choose whether to upload attachments to the blog media folder or to his user root.
* load Quam Plures configuration
require_once dirname( __FILE__ ) . '/../qp_config/_config.php';
require_once $inc_path . '_main.inc.php';
load_class( '_ext/mime_parser/rfc822_addresses.php' );
if( !$Settings->get( 'eblog_enabled' ) )
echo T_( 'Blog by email feature is not enabled.' );
param( 'test', 'integer', 0 );
* Subject of the current email message
* @global string $subject
* post date of current message
* @global string $post_date
* message content of current email that is going to be posted
* @global string $content
* define colour constants for messages
define( 'WARNING', 'orange' );
// if it's not called by a logged in user override test settings
if( !isset ( $current_User ) || !$current_User->check_perm( 'options', 'edit', true ) )
* Whether to do real posting.
* It is set to true if the setting eblog_test_mode is set to false *and*
* the test parameter is not set to 2.
$do_real_posting = (!$Settings->get( 'eblog_test_mode' ) && $test != 2);
echo_message( T_('You configured test mode in the settings or set $test to 2. Nothing will be posted to the database/mediastore nor will your inbox be altered.'), WARNING, 0 );
// TODO: I don't find a header to include for this popup window.
// There should exist one in QP. So right now no valid HTML
$page_title = T_( 'Blog by email' );
echo '<html><head><title>' . $page_title . '</title></head><body>';
* Print out a debugging message with optional HTML color added.
* This function only outputs any additional HTML (colors, <br />) if
* $test is greater than 0.
* @global integer The test level
* @param string $strmessage The message to print
* @param string $color optional colour so use
* @param integer $level optional level to limit output to that level
function echo_message( $strmessage , $color = '', $level = 0 )
if( $test > 0 && $color )
echo '<font color="'. $color. '">';
if( $test > 0 && $color )
* Provide sys_get_temp_dir for older versions of PHP (< 5.2.1).
* code posted on php.net by minghong at gmail dot com
* Based on {@link http://www.phpit.net/article/creating-zip-tar-archives-dynamically-php/2/}
* @return string path to system temporary directory
// Try to get from environment variable
if( !empty( $_ENV['TMP'] ) )
else if( !empty( $_ENV['TMPDIR'] ) )
else if( !empty( $_ENV['TEMP'] ) )
// Detect by creating a temporary file
// Try to use system's temporary directory
// as random name shouldn't exist
* Create a new directory with unique name.
* This creates a new directory below the given path with the given prefix and a random number.
* @param string $dir base path to new directory
* @param string $prefix prefix random number with this
* @param integer $mode permissions to use
* @return string path to created directory
function tempdir( $dir, $prefix = 'tmp', $mode = 0700 )
if( substr( $dir, - 1 ) != '/' ) $dir .= '/';
$path = $dir . $prefix . mt_rand();
} while( !mkdir( $path, $mode ) );
* Process Header information like subject and date of a mail.
* @global string The subject of the current message (write)
* @global string The post date of the current message (write)
* @global object QP settings (read)
* @global integer The test level (read)
* @param array $header header as set by mime_parser_class::Analyze()
* @return bool true if valid subject prefix is detected
// write to these globals
$subject = $header['Subject'];
$ddate = $header['Date'];
$prefix = $Settings->get( 'eblog_subject_prefix' );
// TODO: dh> use strftime (after format validation)? or strptime (PHP>=5.1.0)
// of the form '20 Mar 2002 20:32:37'
if(!preg_match('#^(.{3}, )?(\d{2}) (.{3}) (\d{4}) (\d{2}):(\d{2}):(\d{2})#', $ddate, $match))
if( ! isset ( $dmonths[$match[3]] ) )
$ddate_m = $dmonths[$match[3]];
$ddate_U = mktime( $ddate_H, $ddate_i, $ddate_s, $ddate_m, $ddate_d, $ddate_Y );
$post_date = date( 'Y-m-d H:i:s', $ddate_U );
* process attachments by saving into media directory and optionally creating image tag in post
* @global string message content that is optionally manipulated by adding image tags
* @global bool do we really post?
* @global object global QP settings
* @param array $mailAttachments array containing path to attachment files
* @param string $mediadir path to media directory of blog as seen by file system
* @param string $media_url url to media directory as seen by user
* @param bool $add_img_tags should img tags be added (instead of adding a normal link)
* @return bool true for sucessfull execution
function processAttachments( $mailAttachments, $mediadir, $media_url, $add_img_tags = true )
foreach( $mailAttachments as $attachment )
$filename = strtolower( $attachment['FileName'] );
$filename = tempnam( $mediadir, 'upload' ) . '.' . $attachment['SubType'];
$filename = preg_replace( '/[^a-z0-9\-_.]/', '-', $filename );
// Check valid filename/extension: (includes check for locked filenames)
$return = false; // return: at least one error. try with next attachment
// if file exists count up a number
$prename = substr( $filename, 0, strrpos( $filename, '.' ) ). '-';
$sufname = strrchr( $filename, '.' );
$filename = $prename. $cnt. $sufname;
echo_message( '✘ file already exists. Changing filename to: ' . $filename , WARNING, 2 );
if( !rename( $attachment['DataFile'], $mediadir . $filename ) )
$return = false; // return: at least one error. try with next attachment
$chmod = $Settings->get( 'fm_default_chmod_file' );
$content .= '<img src="'. $media_url. $filename. '" '. $imginfo[3]. ' />';
$content .= '<a href="'. $media_url. $filename. '">'. basename($filename). '</a>';
* look inside message to get title for posting.
* The message could contain a xml-tag <code><title>sample title</title></code> to specify a title for the posting.
* If not tag is found there could be a global $post_default_title containing a global default title.
* If none of these is found then the specified alternate title line is used.
* @param string $content message to search for title tag
* @param string $alternate_title use this string if no title tag is found
* @return string title of posting
* @see $post_default_title
$title = $alternate_title;
echo_message( T_( 'The php_imap extension is not available to PHP on this server. Please load it in php.ini or ask your hosting provider to do so.' ), ERROR, 0 );
* Prepare the connection string.
$mailserver = '{' . $Settings->get( 'eblog_server_host' ) . ':' . $Settings->get( 'eblog_server_port' );
switch($Settings->get('eblog_encrypt'))
switch($Settings->get('eblog_method'))
// imap needs no additional options
if($Settings->get('eblog_novalidatecert'))
$mailserver .= '/novalidate-cert';
// Connect to mail server
$mbox = @imap_open( $mailserver, $Settings->get( 'eblog_username' ), $Settings->get( 'eblog_password' ) );
// Read messages from server
$imap_obj = imap_check( $mbox );
for ( $index = 1; $index <= $imap_obj->Nmsgs; $index++ )
// save mail to disk because with attachments could take up much RAM
imap_savebody( $mbox, $tmpMIME, $index );
$mimeParser->mbox = 0; // Set to 0 for parsing a single message file
$mimeParser->decode_bodies = 1;
$mimeParser->ignore_syntax_errors = 1;
$mimeParser->extract_addresses = 0;
'SaveBody' => $tmpDirMIME, // Save the message body parts to a directory
'SkipBody' => 1, // Do not retrieve or save message body parts
if( !$mimeParser->Decode( $MIMEparameters, $decodedMIME ) )
echo_message( sprintf(T_('MIME message decoding error: %s at position %d.'), $mimeParser->error, $mimeParser->error_position), ERROR, 0 );
if( ! $mimeParser->Analyze( $decodedMIME[0], $parsedMIME ) )
// TODO: handle type == "message" recursively
if( $parsedMIME['Type'] == 'html' ){
foreach ( $parsedMIME['Alternative'] as $alternative ){
if( $alternative['Type'] == 'text' ){
echo_message( 'HTML alternative message part saved as ' . $alternative['DataFile'], INFO, 3 );
break; // stop after first alternative
elseif( $parsedMIME['Type'] == 'text' )
echo_message( 'Plain-text message part saved as ' . $parsedMIME['DataFile'], INFO, 3 );
if( isset ( $parsedMIME['Attachments'] ) && count($parsedMIME['Attachments']) )
foreach( $parsedMIME['Attachments'] as $attachment )
echo_message( 'Attachment: ' . $attachment['FileName'] . ' stored as ' . $attachment['DataFile'], INFO, 3 );
$warning_count = count( $mimeParser->warnings );
foreach ($mimeParser->warnings as $k => $v)
// process body. First fix different line-endings (dos, mac, unix), remove double newlines
$strbody = str_replace( array("\r", "\n\n"), "\n", $strbody );
$a_body = explode( "\n", $strbody, 2 );
// tblue> splitting only into 2 parts allows colons in the user PW
$a_authentication = explode( ':', $a_body[0], 2 );
$content = trim( $a_body[1] );
$user_login = trim( $a_authentication[0] );
// TODO: dh> should the password really get trimmed here?!
$user_pass = isset ($a_authentication[1]) ? trim($a_authentication[1]) : null;
echo_message( '✘ Wrong login or password. First line of text in email must be in the format "username:password".', ERROR, 3 );
$subject = trim( substr($subject, strlen($Settings->get( 'eblog_subject_prefix' ))) );
// remove content after terminator
$eblog_terminator = $Settings->get( 'eblog_body_terminator' );
if( !empty( $eblog_terminator ) &&
($os_terminator = strpos( $content, $eblog_terminator )) !== false)
$content = substr( $content, 0, $os_terminator );
// check_html_sanity needs local user set.
$current_User = & $UserCache->get_by_login( $user_login );
$post_category = $Settings->get( 'eblog_default_category' );
$blog_ID = get_catblog( $post_category ); // TODO: should not die, if cat does not exist!
$Blog = $BlogCache->get_by_ID( $blog_ID, false, false );
echo_message( sprintf( 'Checking permissions for user «%s» to post to Blog #%d', $user_login, $blog_ID ), INFO, 3 );
if( !$current_User->check_perm( 'blog_post!published', 'edit', false, $blog_ID )
|| ( $hasAttachment && !$current_User->check_perm( 'files', 'add', false ) )
$mediadir = $Blog->get_media_dir();
processAttachments( $parsedMIME['Attachments'], $mediadir, $Blog->get_media_url(), $Settings->get('eblog_add_imgtag') );
// CHECK and FORMAT content
if( ( $error = $Messages->get_string( T_( 'Cannot post, please correct these errors:' ), '', 'error' ) ) )
$Messages->clear( 'error' );
// INSERT NEW POST INTO DB:
$edited_Item = new Item();
$post_ID = $edited_Item->insert( $current_User->ID, $post_title, $content, $post_date, $post_category, array(), 'published', $current_User->locale );
// Execute or schedule notifications & pings:
$edited_Item->handle_post_processing();
imap_delete( $mbox, $index );
// TODO: I don't find a footer to include in this popup. QP should include one...
|