<?php
/**
* This file implements the post by mail support functions.
*
* b2evolution - {@link http://b2evolution.net/}
* Released under GNU GPL License - {@link http://b2evolution.net/about/gnu-gpl-license}
*
* @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
*
* @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
*
* @package admin
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
/**
* Print out a debugging message with optional HTML color added
*
* @param string Message
* @param boolean TRUE if it is called from cron
* @param string Message type: 'success', 'warning', 'error', 'note', NULL - to use default text without addition style color
*/
function pbm_msg( $message, $cron = false, $type = NULL )
{
global $is_web, $pbm_messages;
// Log all messages to $pbm_messages no matter if we are in cron mode or not
// We may use this report later, display or send to the blog owner
$pbm_messages[] = $message;
if( $cron )
{ // Append a message to cron log if we are in cron mode:
cron_log_append( $message, $type );
}
if( $type == 'error' )
{ // Store an error message in system log:
syslog_insert( 'Get posts by email: '.$message, 'error' );
}
}
/**
* Connect to an IMAP or POP mail server
*
* @param boolean TRUE if script is executed by cron
* @return resource $mbox
*/
function pbm_connect( $cron = false )
{
if( ! extension_loaded( 'imap' ) )
{ // Exit here if imap extension is not loaded:
pbm_msg( '<b class="red">'.( 'IMAP extension is NOT loaded! ').'</b>', $cron );
return false;
}
global $Settings;
$host = $Settings->get('eblog_server_host').':'.$Settings->get('eblog_server_port');
$mailserver = '{'.$host;
pbm_msg( sprintf( ('Connecting and authenticating to mail server %s'), '<b>'.$host.'</b>' ), $cron );
switch( $Settings->get('eblog_encrypt') )
{
case 'ssl':
$mailserver .= '/ssl';
break;
case 'tls':
$mailserver .= '/tls';
break;
case 'none':
default:
$mailserver .= '/notls';
break;
}
switch( $Settings->get('eblog_method') )
{
case 'pop3':
case 'pop3a':
$mailserver .= '/pop3';
break;
case 'imap':
default:
// imap needs no additional options
break;
}
if( $Settings->get('eblog_novalidatecert') )
{
$mailserver .= '/novalidate-cert';
}
$mailserver .= '}INBOX';
// Connect to mail server (one retry)
$mbox = @imap_open( $mailserver, $Settings->get('eblog_username'), $Settings->get('eblog_password'), NULL, 1 );
if( is_null(@get_resource_type($mbox)) )
{ // Not a resource
$error = imap_errors();
if( is_array($error) )
{
$error = implode( "<br />\n", $error );
}
pbm_msg( sprintf( ('Connection failed: %s'), $error ), $cron, 'error' );
return false;
}
pbm_msg( '<b class="green">'.('Successfully connected!').'</b>', $cron );
@imap_errors();
return $mbox;
}
/**
* Read messages from server and create posts
*
* @param resource $mbox created by pbm_connect() (by reference)
* @param integer the number of messages to process
* @param boolean TRUE if script is executed by cron
* @return boolean true on success
*/
function pbm_process_messages( & $mbox, $limit, $cron = false )
{
global $Settings, $debug;
global $pbm_item_files, $pbm_messages, $pbm_items, $post_cntr, $del_cntr, $is_cron_mode;
if( empty( $is_cron_mode ) )
{ // This may take a very long time if there are many messages; No execution time limit:
// (don't apply this for cron job because it uses a setting "Max execution time")
set_max_execution_time( 0 );
}
// Are we in test mode?
$test_mode_on = $Settings->get('eblog_test_mode');
$post_cntr = 0;
$del_cntr = 0;
for( $index = 1; $index <= $limit; $index++ )
{ // Repeat for as many messages as allowed...
pbm_msg( '<hr /><h3>'.sprintf( ('Processing message %s:'), '#'.$index ).'</h3>', $cron );
$html_body = '';
$strbody = '';
$hasAttachment = false;
$hasRelated = false;
$pbm_item_files = array(); // reset the value for each new Item
// Save email to a temporary file on hard drive, otherwise BIG attachments may take a lot of RAM:
if( ! ($tmpMIME = tempnam( sys_get_temp_dir(), 'b2evoMail' )) )
{
pbm_msg( ('Could not create temporary file.'), $cron );
continue;
}
// Save the whole body of a specific message from the mailbox:
imap_savebody( $mbox, $tmpMIME, $index );
// Create random temp directory for message parts:
$tmpDirMIME = pbm_tempdir( sys_get_temp_dir(), 'b2evo_' );
// Instanciate mime_parser.php library:
$mimeParser = new mime_parser_class();
$mimeParser->mbox = 0; // Set to 0 for parsing a *single* RFC 2822 message
// NOTE: Use function mb_decode_mimeheader() instead of decoder from mime_parser_class,
// because the class cannot properly decode KOI8-R headers:
$mimeParser->decode_headers = ! function_exists( 'mb_decode_mimeheader' ); // Set to 1 if it is necessary to decode message headers that may have non-ASCII characters and use other character set encodings
$mimeParser->ignore_syntax_errors = 1; // ignore syntax errors in malformed messages.
$mimeParser->extract_addresses = 0;
$MIMEparameters = array(
'File' => $tmpMIME,
'SaveBody' => $tmpDirMIME, // Save message body parts to a directory
'SkipBody' => 1, // Do not retrieve or save message body parts
);
// Associative array to specify parameters for the messagedata parsing and decoding operation.
$MIMEparameters = array(
'File' => $tmpMIME, // Name of the file from which the message data will be read.
'SaveBody' => $tmpDirMIME, // Save message body parts to a directory
'SkipBody' => 1, // 1 means the information about the message body part structure is returned in $decodedMIME below but it does not return any body data.
);
// STEP 1: Parse and decode message data and retrieve its structure:
if( !$mimeParser->Decode( $MIMEparameters, $decodedMIME ) )
{ // error:
pbm_msg( sprintf( ('MIME message decoding error: %s at position %d.'), $mimeParser->error, $mimeParser->error_position ), $cron, 'error' );
rmdir_r( $tmpDirMIME );
unlink( $tmpMIME );
continue;
}
else
{ // the specified message data was parsed successfully:
pbm_msg( ('MIME message decoding successful'), $cron );
// STEP 2: Analyze (the first) parsed message to describe its contents:
if( ! $mimeParser->Analyze( $decodedMIME[0], $parsedMIME ) )
{ // error:
pbm_msg( sprintf( ('MIME message analyze error: %s'), $mimeParser->error ), $cron, 'error' );
rmdir_r( $tmpDirMIME );
unlink( $tmpMIME );
continue;
}
// Get message $subject and $post_date from headers (by reference)
if( ! pbm_process_header( $parsedMIME, $subject, $post_date, $cron ) )
{ // Couldn't process message headers:
rmdir_r( $tmpDirMIME );
unlink( $tmpMIME );
continue;
}
// TODO: handle type == "message" recursively
// sam2kb> For some reason imap_qprint() demages HTML text... needs more testing
// yura> I replaced imap_qprint() with quoted_printable_decode() to avoid notices about invalid quoted-printable sequence
// yura> imap_qprint() and quoted_printable_decode() do empty the message text, thus they were deleted.
if( $parsedMIME['Type'] == 'html' )
{ // Mail is HTML:
if( $Settings->get('eblog_html_enabled') )
{ // HTML posting enabled
if( $debug )
{ // Display this info only in debug mode:
pbm_msg( sprintf( ('HTML message part saved as %s'), $parsedMIME['DataFile'] ), $cron );
}
$html_body = file_get_contents( $parsedMIME['DataFile'] );
}
foreach( $parsedMIME['Alternative'] as $alternative )
{ // First try to get HTML alternative (when possible)
if( $alternative['Type'] == 'html' && $Settings->get('eblog_html_enabled') )
{ // HTML text:
if( $debug )
{ // Display this info only in debug mode:
pbm_msg( sprintf( ('HTML alternative message part saved as %s'), $alternative['DataFile'] ), $cron );
}
$strbody = file_get_contents( $alternative['DataFile'] );
break; // stop after first alternative
}
elseif( $alternative['Type'] == 'text' )
{ // Plain text:
if( $debug )
{ // Display this info only in debug mode:
pbm_msg( sprintf( ('Text alternative message part saved as %s'), $alternative['DataFile'] ), $cron );
}
$strbody = file_get_contents( $alternative['DataFile'] );
break; // stop after first alternative
}
}
}
elseif( $parsedMIME['Type'] == 'text' )
{ // Mail is plain text:
if( $debug )
{ // Display this info only in debug mode:
pbm_msg( sprintf( ('Plain-text message part saved as %s'), $parsedMIME['DataFile'] ), $cron );
}
$strbody = file_get_contents( $parsedMIME['DataFile'] );
}
// Check for attachments:
if( ! empty( $parsedMIME['Attachments'] ) )
{
$hasAttachment = true;
foreach( $parsedMIME['Attachments'] as $file )
{
pbm_msg( sprintf( ('Attachment: %s stored as %s'), $file['FileName'], $file['DataFile'] ), $cron );
}
}
// Check for inline images:
if( ! empty( $parsedMIME['Related'] ) )
{
$hasRelated = true;
foreach( $parsedMIME['Related'] as $file )
{
pbm_msg( sprintf( ('Related file with content ID: %s stored as %s'), $file['ContentID'], $file['DataFile'] ), $cron );
}
}
if( count( $mimeParser->warnings ) > 0 )
{
pbm_msg( '<h4>'.sprintf( ('%d warnings during decode:'), count( $mimeParser->warnings ) ).'</h4>', $cron );
foreach( $mimeParser->warnings as $k => $v )
{
pbm_msg( sprintf( ('Warning: %s at position %s'), $v, $k ), $cron );
}
}
}
unlink( $tmpMIME );
if( empty( $html_body ) )
{ // Plain-text message
pbm_msg( sprintf( ('Message type: %s'), 'TEXT' ), $cron );
pbm_msg( sprintf( ('Message body: %s'), '<pre style="font-size:10px">'.htmlspecialchars( $strbody ).'</pre>' ), $cron );
// Process body. First fix different line-endings (dos, mac, unix), remove double newlines
$content = str_replace( array( "\r", "\n\n" ), "\n", trim( $strbody ) );
// First see if there's an <auth> tag with login and password
if( ( $auth = pbm_get_auth_tag( $content ) ) === false )
{ // No <auth> tag, let's detect legacy "username:password" on the first line
$a_body = explode( "\n", $content, 2 );
// tblue> splitting only into 2 parts allows colons in the user PW
// Note: login and password cannot include '<' !
$auth = explode( ':', strip_tags($a_body[0]), 2 );
// Drop the first line with username and password
$content = $a_body[1];
}
}
else
{ // HTML message
pbm_msg( sprintf( ('Message type: %s'), 'HTML' ), $cron );
if( ( $parsed_message = pbm_prepare_html_message( $html_body, $cron ) ) === false )
{ // No 'auth' tag provided, skip to the next message
rmdir_r( $tmpDirMIME );
continue;
}
list($auth, $content) = $parsed_message;
}
// TODO: dh> should the password really get trimmed here?!
$user_pass = isset($auth[1]) ? trim( $auth[1] ) : NULL;
$user_login = trim( utf8_strtolower( $auth[0] ) );
if( empty($user_login) || empty($user_pass) )
{
pbm_msg( sprintf( ('Please add username and password in message body in format %s.'),
'"<auth>username:password</auth>"' ), $cron );
rmdir_r( $tmpDirMIME );
continue;
}
// Authenticate user
pbm_msg( ('Authenticating User').': «'.$user_login.'»', $cron );
$pbmUser = & pbm_validate_user_password( $user_login, $user_pass );
if( ! $pbmUser )
{
pbm_msg( sprintf( ( 'Authentication failed for user «%s»' ), htmlspecialchars( $user_login ) ), $cron, 'error' );
rmdir_r( $tmpDirMIME );
continue;
}
$pbmUser->get_Group(); // Load group
if( ! empty($is_cron_mode) )
{ // Assign current User if we are in cron mode. This is needed in order to check user permissions
global $current_User;
$current_User = clone $pbmUser;
}
// Activate User's locale
locale_activate( $pbmUser->get('locale') );
pbm_msg( '<b class="green">'.('Success').'</b>', $cron );
if( $post_categories = xmlrpc_getpostcategories( $content ) )
{
$main_cat_ID = array_shift($post_categories);
$extra_cat_IDs = $post_categories;
pbm_msg( ('Extra categories').': '.implode( ', ', $extra_cat_IDs ), $cron );
}
else
{
$main_cat_ID = $Settings->get('eblog_default_category');
$extra_cat_IDs = array();
}
pbm_msg( ('Main category ID').': '.$main_cat_ID, $cron );
$ChapterCache = & get_ChapterCache();
$pbmChapter = & $ChapterCache->get_by_ID( $main_cat_ID, false, false );
if( empty($pbmChapter) )
{
pbm_msg( sprintf( ('Requested category %s does not exist!'), $main_cat_ID ), $cron );
rmdir_r( $tmpDirMIME );
continue;
}
$blog_ID = $pbmChapter->blog_ID;
pbm_msg( ('Blog ID').': '.$blog_ID, $cron );
$BlogCache = & get_BlogCache();
$pbmBlog = & $BlogCache->get_by_ID( $blog_ID, false, false );
if( empty($pbmBlog) )
{
pbm_msg( sprintf( ('Requested collection %s does not exist!'), $blog_ID ), $cron );
rmdir_r( $tmpDirMIME );
continue;
}
// Check permission:
pbm_msg( sprintf( ('Checking permissions for User «%s» to post to Collection #%d'), $user_login, $blog_ID ), $cron );
if( !$pbmUser->check_perm( 'blog_post!published', 'edit', false, $blog_ID ) )
{
pbm_msg( ('Permission denied.'), $cron );
rmdir_r( $tmpDirMIME );
continue;
}
if( ($hasAttachment || $hasRelated) && !$pbmUser->check_perm( 'files', 'add', false, $blog_ID ) )
{
pbm_msg( ( 'You have no permission to add/upload files.' ), $cron );
rmdir_r( $tmpDirMIME );
continue;
}
pbm_msg( '<b class="green">'.('Success').'</b>', $cron );
// Remove content after terminator
$eblog_terminator = $Settings->get('eblog_body_terminator');
if( !empty( $eblog_terminator ) && ($os_terminator = utf8_strpos( $content, $eblog_terminator )) !== false )
{
$content = utf8_substr( $content, 0, $os_terminator );
}
$post_title = pbm_get_post_title( $content, $subject );
// Remove 'title' and 'category' tags
$content = xmlrpc_removepostdata( $content );
// Remove <br> tags from string start and end
// We do it here because there might be extra <br> left after deletion of <auth>, <category> and <title> tags
$content = preg_replace( array( '~^(\s*<br[\s/]*>\s*){1,}~i', '~(\s*<br[\s/]*>\s*){1,}$~i' ), '', $content );
if( $hasAttachment || $hasRelated )
{ // Handle attachments
if( isset($GLOBALS['files_Module']) )
{
if( $mediadir = $pbmBlog->get_media_dir() )
{
if( $hasAttachment )
{
pbm_process_attachments( $content, $parsedMIME['Attachments'], $mediadir,
$pbmBlog->get_media_url(), $Settings->get('eblog_add_imgtag'), 'attach', $cron );
}
if( $hasRelated )
{
pbm_process_attachments( $content, $parsedMIME['Related'], $mediadir,
$pbmBlog->get_media_url(), true, 'related', $cron );
}
}
else
{
pbm_msg( ('Unable to access media directory. No attachments processed.'), $cron );
}
}
else
{
pbm_msg( ('Files module is disabled or missing!'), $cron );
}
}
// CHECK and FORMAT content
global $Plugins;
$renderer_params = array( 'Blog' => & $pbmBlog, 'setting_name' => 'coll_apply_rendering' );
$renderers = $Plugins->validate_renderer_list( $Settings->get('eblog_renderers'), $renderer_params );
pbm_msg( sprintf( ('Applying the following text renderers: %s'), implode( ', ', $renderers ) ), $cron );
// Do some optional filtering on the content
// Typically stuff that will help the content to validate
// Useful for code display
// Will probably be used for validation also
// + APPLY RENDERING from Rendering Plugins:
$Plugins_admin = & get_Plugins_admin();
$params = array( 'object_type' => 'Item', 'object_Blog' => & $pbmBlog );
$Plugins_admin->filter_contents( $post_title /* by ref */, $content /* by ref */, $renderers, $params );
pbm_msg( sprintf( ('Filtered post content: %s'), '<pre style="font-size:10px">'.htmlspecialchars( $content ).'</pre>' ), $cron );
$context = $Settings->get('eblog_html_tag_limit') ? 'commenting' : 'posting';
$post_title = check_html_sanity( $post_title, $context, $pbmUser );
$content = check_html_sanity( $content, $context, $pbmUser );
global $Messages;
if( $Messages->has_errors() )
{
// Make it easier for user to find and correct the errors
pbm_msg( "\n".sprintf( ('Processing message: %s'), $post_title ), $cron );
pbm_msg( $Messages->get_string( ('Cannot post, please correct these errors:'), 'error' ), $cron, 'error' );
$Messages->clear();
rmdir_r( $tmpDirMIME );
continue;
}
if( $test_mode_on )
{ // Test mode
pbm_msg( '<b class="green">'.('It looks like the post can be successfully saved in the database. However we will not do it in test mode.').'</b>', $cron );
}
else
{
load_class( 'items/model/_item.class.php', 'Item' );
global $pbm_items, $DB, $localtimenow;
$post_status = 'published';
pbm_msg( '<h4>'.sprintf( ('Saving item "%s" in the database'), $post_title ).'</h4>', $cron );
// INSERT NEW POST INTO DB:
$edited_Item = new Item();
$edited_Item->set_creator_User( $pbmUser );
$edited_Item->set( $edited_Item->lasteditor_field, $pbmUser->ID );
$edited_Item->set( 'title', $post_title );
$edited_Item->set( 'content', $content );
$edited_Item->set( 'datestart', $post_date );
$edited_Item->set( 'datemodified', date('Y-m-d H:i:s',$localtimenow) );
$edited_Item->set( 'main_cat_ID', $main_cat_ID );
$edited_Item->set( 'extra_cat_IDs', $extra_cat_IDs );
$edited_Item->set( 'status', $post_status );
$edited_Item->set( 'locale', $pbmUser->locale );
$edited_Item->set( 'renderers', $renderers );
// INSERT INTO DB:
$edited_Item->dbinsert();
pbm_msg( sprintf( ('Item created?: %s'), ( isset( $edited_Item->ID ) ? 'yes' : 'no' ) ), $cron );
// Execute or schedule notifications & pings:
$edited_Item->handle_notifications( $pbmUser->ID, true );
if( !empty($pbm_item_files) )
{ // Attach files
$FileCache = & get_FileCache();
$order = 1;
foreach( $pbm_item_files as $filename )
{
pbm_msg( sprintf( ('Saving file "%s" in the database'), $filename ), $cron );
$pbmFile = & $FileCache->get_by_root_and_path( 'collection', $pbmBlog->ID, $filename );
$pbmFile->meta = 'notfound'; // Save time and don't try to load meta from DB, it's not there anyway
$pbmFile->dbsave();
pbm_msg( sprintf( ('File saved?: %s'), ( isset( $pbmFile->ID ) ? 'yes' : 'no' ) ), $cron );
pbm_msg( sprintf( ('Attaching file "%s" to the post'), $filename ), $cron );
// Let's make the link!
$pbmLink = new Link();
$pbmLink->set( 'itm_ID', $edited_Item->ID );
$pbmLink->set( 'file_ID', $pbmFile->ID );
$pbmLink->set( 'position', 'aftermore' );
$pbmLink->set( 'order', $order++ );
$pbmLink->dbinsert();
pbm_msg( sprintf( ('File attached?: %s'), ( isset( $pbmLink->ID ) ? 'yes' : 'no' ) ), $cron );
}
// Invalidate blog's media BlockCache
BlockCache::invalidate_key( 'media_coll_ID', $edited_Item->get_blog_ID() );
}
// Save posted items sorted by author user for reports
$pbm_items['user_'.$pbmUser->ID][] = $edited_Item;
++$post_cntr;
}
pbm_msg( ('Message posting successful'), $cron );
// Delete temporary directory
rmdir_r( $tmpDirMIME );
if( ! $test_mode_on && $Settings->get('eblog_delete_emails') )
{
pbm_msg( sprintf( ('Marking message for deletion from inbox: %s'), $index ), $cron );
imap_delete( $mbox, $index );
++$del_cntr;
}
}
// Expunge messages marked for deletion
imap_expunge($mbox);
return true;
}
/**
* Process Header information like subject and date of a mail.
*
* @param array $header header as set by mime_parser_class::Analyze()
* @param string message subject by reference
* @param string message date by reference
* @param boolean TRUE if script is executed by cron
* @return bool true if valid subject prefix is detected
*/
function pbm_process_header( $header, & $subject, & $post_date, $cron = false )
{
global $Settings;
$subject = $header['Subject'];
if( function_exists( 'mb_decode_mimeheader' ) )
{ // Decode email subject:
$subject = mb_decode_mimeheader( $subject );
}
$ddate = $header['Date'];
$prefix = $Settings->get( 'eblog_subject_prefix' );
pbm_msg( ('Subject').': '.$subject, $cron );
if( utf8_substr($subject, 0, utf8_strlen($prefix)) !== $prefix )
{
pbm_msg( sprintf( ('Subject prefix is not "%s", skip this email'), $prefix ), $cron );
return false;
}
$subject = utf8_substr($subject, utf8_strlen($prefix));
// Parse Date
if( !preg_match('#^(.{3}, )?(\d{2}) (.{3}) (\d{4}) (\d{2}):(\d{2}):(\d{2})#', $ddate, $match) )
{
$ddate_U = @strtotime($ddate);
if( empty($ddate_U) || strlen($ddate_U) < 2 )
{
pbm_msg( sprintf( ('Could not parse date header "%s"'), $ddate ), $cron );
return false;
}
}
if( empty($ddate_U) )
{
$dmonths = array(
'Jan' => 1,
'Feb' => 2,
'Mar' => 3,
'Apr' => 4,
'May' => 5,
'Jun' => 6,
'Jul' => 7,
'Aug' => 8,
'Sep' => 9,
'Oct' => 10,
'Nov' => 11,
'Dec' => 12,
);
$ddate_H = $match[5];
$ddate_i = $match[6];
$ddate_s = $match[7];
if( ! isset( $dmonths[$match[3]] ) )
{
pbm_msg( ('Invalid month name in message date string.'), $cron );
return false;
}
$ddate_m = $dmonths[$match[3]];
$ddate_d = $match[2];
$ddate_Y = $match[4];
$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 );
return true;
}
/**
* process attachments by saving into media directory and optionally creating image tag in post
*
* @param string message content that is optionally manipulated by adding image tags (by reference)
* @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 to the post (instead of linking through the file manager)
* @param string $type defines attachment type: 'attach' or 'related'
* @param boolean TRUE if script is executed by cron
*/
function pbm_process_attachments( & $content, $mailAttachments, $mediadir, $media_url, $add_img_tags = true, $type = 'attach', $cron = false )
{
global $Settings, $pbm_item_files, $filename_max_length;
pbm_msg( '<h4>'.('Processing attachments').'</h4>', $cron );
foreach( $mailAttachments as $attachment )
{
if( isset($attachment['FileName']) )
{
$filename = trim( utf8_strtolower($attachment['FileName']) );
}
else
{ // Related attachments may not have file name, we'll generate one below
$filename = '';
}
if( $filename == '' )
{
$filename = 'upload_'.uniqid().'.'.$attachment['SubType'];
pbm_msg( sprintf( ('Attachment without name. Using "%s".'), htmlspecialchars( $filename ) ), $cron );
}
// Check valid filename/extension: (includes check for locked filenames)
if( $error_filename = process_filename( $filename, true ) )
{
pbm_msg( ('Invalid filename').': '.$error_filename, $cron, 'error' );
syslog_insert( sprintf( 'The posted by mail file %s has an unrecognized extension', '[['.$filename.']]' ), 'warning', 'file' );
continue;
}
// If file exists count up a number
$cnt = 0;
$prename = substr( $filename, 0, strrpos( $filename, '.' ) ).'-';
$sufname = strrchr( $filename, '.' );
$error_in_filename = false;
while( file_exists( $mediadir.$filename ) )
{
$filename = $prename.$cnt.$sufname;
if( strlen( $filename ) > $filename_max_length )
{ // This is a special case, when the filename is longer then the maximum allowed
// Cut as many characters as required before the counter on the file name
$filename = fix_filename_length( $filename, strlen( $prename ) - 1 );
if( $error_in_filename = process_filename( $filename, true ) )
{ // The file name is not valid, this is an unexpected situation, because the file name was already validated before
pbm_msg( ('Invalid filename').': '.$error_filename, $cron, 'error' );
syslog_insert( sprintf( 'The posted by mail file %s has an unrecognized extension', '[['.$filename.']]' ), 'warning', 'file' );
break;
}
}
++$cnt;
}
if( $error_in_filename )
{ // Don't create file with invalid file name
continue;
}
pbm_msg( sprintf( ('New file name is %s'), '<b>'.$filename.'</b>' ), $cron );
$imginfo = NULL;
if( ! $Settings->get('eblog_test_mode') )
{
pbm_msg( sprintf( ('Saving file to: %s'), htmlspecialchars( $mediadir.$filename ) ), $cron );
if( !copy( $attachment['DataFile'], $mediadir.$filename ) )
{
pbm_msg( sprintf( ('Unable to copy uploaded file to %s'), htmlspecialchars( $mediadir.$filename ) ), $cron );
continue;
}
// chmod uploaded file:
$chmod = $Settings->get('fm_default_chmod_file');
@chmod( $mediadir.$filename, octdec( $chmod ) );
$imginfo = @getimagesize($mediadir.$filename);
pbm_msg( sprintf( ('Is this an image?: %s'), ( is_array( $imginfo ) ? 'yes' : 'no' ) ), $cron );
}
if( $type == 'attach' )
{
$content .= "\n";
if( is_array($imginfo) && $add_img_tags )
{
$content .= '<img src="'.$media_url.$filename.'" '.$imginfo[3].' />';
}
else
{
pbm_msg( sprintf( ('The file %s will be attached to the post later, after we save the post in the database.'), '<b>'.$filename.'</b>' ), $cron );
$pbm_item_files[] = $filename;
}
$content .= "\n";
}
elseif( !empty($attachment['ContentID']) )
{ // Replace relative "cid:xxxxx" URIs with absolute URLs to media files
$content = str_replace( 'cid:'.$attachment['ContentID'], $media_url.$filename, $content );
}
}
}
/**
* 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 no tag is found we will try to get the default title from settings.
* 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
*
*/
function pbm_get_post_title( $content, $alternate_title )
{
global $Settings;
if( preg_match('~<title>(.+?)</title>~is', $content, $matchtitle) )
{
$title = $matchtitle[1];
}
else
{
$title = $Settings->get('eblog_default_title');
}
if( $title == '' )
{
$title = $alternate_title;
}
return $title;
}
/**
* Extract <auth> tag with login and password
*
* @param string Message body (by reference)
* @return array login and password
*/
function pbm_get_auth_tag( & $content )
{
if( preg_match( '~<(auth)>(.+?)</\\1>~is', $content, $match ) )
{
// tblue> splitting only into 2 parts allows colons in the user PW
// Note: login and password cannot include '<' !
$auth = explode( ':', strip_tags($match[2]), 2 );
// Delete 'auth' tag from post content
$content = preg_replace( '~<(auth)>(.+?)</\\1>~is', '', $content );
return $auth;
}
return false;
}
/**
* Prepare html message
*
* @param string Message
* @param boolean TRUE if script is executed by cron
* @return string Content
*/
function pbm_prepare_html_message( $message, $cron = false )
{
pbm_msg( sprintf( ('Message body (original): %s'), '<pre style="font-size:10px">'.htmlspecialchars( $message ).'</pre>' ), $cron );
$marker = 0;
if( preg_match( '~<body[^>]*>(.*?)</body>~is', $message, $result ) )
{ // First see if we can get contents of <body> tag
$content = $result[1];
$marker = 1;
}
elseif( preg_match( '~<html[^>]*>(.*?)</html>~is', $message, $result ) )
{ // <body> was not found, use <html> contents and delete <head> section from it
$content = preg_replace( '~<head[^>]*>(.*?)</head>~is', '', $result[1] );
$marker = 1;
}
if( empty($marker) )
{ // None of the above methods worked, just use the original message body
$content = $message;
}
// First fix different line-endings (dos, mac, unix), remove double newlines
$content = str_replace( array("\r", "\n\n"), "\n", trim($content) );
// Decode 'category', 'title' and 'auth' tags
$content = preg_replace( '~<(/)?(category|title|auth)>~i', '<\\1\\2>', $content );
if( ($auth = pbm_get_auth_tag($content)) === false )
{ // No 'auth' tag provided, exit
pbm_msg( sprintf( ('<auth> tag not found! Please add username and password in message body in format %s.'),
'"<auth>username:password</auth>"' ), $cron );
return false;
}
// Balance tags
$content = balance_tags($content);
// Remove markup that cause validator errors
$patterns = array(
'~ moz-do-not-send="true"~', // Thunderbird inline image with absolute "src"
'~ class="moz-signature" cols="\d+"~', // Thunderbird signature in HTML message
'~ goomoji="[^"]+"~', // Gmail smilies
);
$content = preg_replace( $patterns, '', $content );
pbm_msg( sprintf( ('Message body (processed): %s'), '<pre style="font-size:10px">'.htmlspecialchars( $content ).'</pre>' ), $cron );
return array( $auth, $content );
}
function pbm_validate_user_password( $user_login, $user_pass )
{
$UserCache = & get_UserCache();
$User = & $UserCache->get_by_login( $user_login );
if( ! $User )
{
return false;
}
// First check unhashed password
if( ! $User->check_password( $user_pass, false ) )
{
if( preg_match( '~^[a-f0-9]{32}$~i', $user_pass ) )
{ // This is a hashed password, see if it's valid
// We check it here because some crazy user may use a real 32-chars password!
if( $User->check_password( $user_pass, true ) )
{ // Valid password
return $User;
}
}
return false;
}
return $User;
}
/**
* 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 pbm_tempdir( $dir, $prefix = 'tmp', $mode = 0700 )
{
// Add trailing slash
$dir = trailing_slash($dir);
do { $path = $dir.$prefix.mt_rand(); } while( ! evo_mkdir( $path, $mode ) );
return $path;
}
?>