<?php /*

 ocPortal
 Copyright (c) ocProducts, 2004-2012

 See text/EN/licence.txt for full licencing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license		http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright	ocProducts Ltd
 * @package		core
 */

/**
 * Standard code module initialisation function.
 */
function init__mail()
{
	require_lang('mail');

	global $SENDING_MAIL,$EMAIL_ATTACHMENTS;
	$SENDING_MAIL=false;
	$EMAIL_ATTACHMENTS=array();
}

/**
 * Replace an HTML img tag such that it is cid'd. Callback for preg_replace_callback.
 *
 * @param  array			Matches
 * @return string			Replacement
 */
function _mail_img_rep_callback($matches)
{
	global $CID_IMG_ATTACHMENT;
	$cid=uniqid('').'@'.get_domain();
	$CID_IMG_ATTACHMENT[$cid]=$matches[2];
	return '<img '.$matches[1].'src="cid:'.$cid.'"';
}

/**
 * Replace CSS image references such that it is cid'd. Callback for preg_replace_callback.
 *
 * @param  array			Matches
 * @return string			Replacement
 */
function _mail_css_rep_callback($matches)
{
	global $CID_IMG_ATTACHMENT;
	$cid=uniqid('').'@'.get_domain();
	if ((basename($matches[1])!='keyboard.png') && (basename($matches[1])!='email_link.png') && (basename($matches[1])!='external_link.png'))
		$CID_IMG_ATTACHMENT[$cid]=$matches[1];
	return 'url(\'cid:'.$cid.'\')';
}

/**
 * Indent text lines. Callback for preg_replace_callback.
 *
 * @param  array			Matches
 * @return string			Replacement
 */
function _indent_callback($matches)
{
	return '      '.str_replace(chr(10),chr(10).'      ',$matches[1]);
}

/**
 * Make titles readable. Callback for preg_replace_callback.
 *
 * @param  array			Matches
 * @return string			Replacement
 */
function _title_callback($matches)
{
	$symbol='-';
	if (strpos($matches[1],'1')!==false) $symbol='=';
	return $matches[2].chr(10).str_repeat($symbol,strlen($matches[2]));
}

/**
 * Make boxes readable. Callback for preg_replace_callback.
 *
 * @param  array			Matches
 * @return string			Replacement
 */
function _box_callback($matches)
{
	return $matches[1].chr(10).str_repeat('-',strlen($matches[1])).chr(10).$matches[2];
}

/**
 * Make some Comcode more readable.
 *
 * @param  string			Comcode text to change
 * @return string			Clean text
 */
function comcode_to_clean_text($message_plain)
{
	//$message_plain=str_replace("\n",'',$message_plain);

	if ((strpos($message_plain,'[')===false) && (strpos($message_plain,'{')===false)) return $message_plain;

	require_code('tempcode_compiler');
	if ((strpos($message_plain,'[code')===false) && (strpos($message_plain,'[no_parse')===false) && (strpos($message_plain,'[tt')===false))
	{
		// Change username links to plain username namings
		$message_plain=preg_replace('#\{\{([^\}\{]*)\}\}#','\1',$message_plain);

		// Remove directives etc
		do
		{
			$before=$message_plain;
			$message_plain=preg_replace('#\{([^\}\{]*)\}#','',$message_plain);
		}
		while ($message_plain!=$before);

		$message_plain=static_evaluate_tempcode(template_to_tempcode($message_plain,0,false,'',NULL,NULL,true));
	}

	$match=array();
	if (preg_match("#\[semihtml\](.*)\[\/semihtml\]#Us",$message_plain,$match)!=0)
	{
		require_code('comcode_from_html');
		$message_plain=str_replace($match[0],semihtml_to_comcode($match[0],true),$message_plain);
	}
	if (preg_match("#\[html\](.*)\[\/html\]#Us",$message_plain,$match)!=0)
	{
		require_code('comcode_from_html');
		$message_plain=str_replace($match[0],semihtml_to_comcode($match[0],true),$message_plain);
	}
	$message_plain=array_key_exists(1,$match) ? $match[1] : $message_plain;

	$message_plain=preg_replace("#\[url=\"([^\"]*)\"(.*)\]([^\[\]]*)\[/url\]#",'${1} (${3})',$message_plain);

	$message_plain=preg_replace("#\[img(.*)\]([^\[\]]*)\[/img\]#",'',$message_plain);

	$message_plain=strip_html($message_plain);

	$message_plain=str_replace(']http',']'.chr(10).'http',str_replace('[/url]',chr(10).'[/url]',$message_plain));
	$message_plain=preg_replace('#\[random [^=]*="([^"]*)"[^\]]*\].*\[/random\]#Us','${1}',$message_plain);
	$message_plain=preg_replace('#\[abbr="([^"]*)"[^\]]*\].*\[/abbr\]#Us','${1}',$message_plain);
	$message_plain=preg_replace_callback('#\[indent[^\]]*\](.*)\[/indent\]#Us','_indent_callback',$message_plain);
	$message_plain=preg_replace_callback('#\[title([^\]])*\](.*)\[/title\]#Us','_title_callback',$message_plain);
	$message_plain=preg_replace_callback('#\[box="([^"]*)"[^\]]*\](.*)\[/box\]#Us','_box_callback',$message_plain);
	$tags_to_strip_inards=array('if_in_group','snapback','post','thread','topic','include','staff_note','attachment','attachment_safe','contents','block','random');
	foreach ($tags_to_strip_inards as $s)
	{
		$message_plain=preg_replace('#\['.$s.'[^\]]*\].*\[/'.$s.'\]#Us','',$message_plain);
	}
	$message_plain=preg_replace('#\[surround="accessibility_hidden"\].*\[/surround\]#Us','',$message_plain);
	$tags_to_strip=array('surround','ticker','jumping','right','center','left','align','list','concepts','html','semihtml','concept','size','color','font','tt','address','sup','sub','box');
	foreach ($tags_to_strip as $s)
	{
		$message_plain=preg_replace('#\['.$s.'[^\]]*\](.*)\[/'.$s.'\]#U','${1}',$message_plain);
	}
	$message_plain=str_replace(
		array('[/*]','[*]','[list]'.chr(10),chr(10).'[/list]','[list]','[/list]','[b]','[/b]','[i]','[/i]','[u]','[/u]','[highlight]','[/highlight]'),
		array('',' - ','','','','','**','**','*','*','__','__','***','***'),
		$message_plain);
	$message_plain=preg_replace('#\[list[^\[\]]*\]#','',$message_plain);

	$message_plain=preg_replace('#\{\$,[^\{\}]*\}#','',$message_plain);

	return trim($message_plain);
}

/*
What headers to use can easily confuse. Here is a guide...

return-path		(aka envelope-from aka reverse-path)		SET BY SMTP SERVER, NOT HEADER
from				Who actually sent, SMTP-wise (should be accurate, as may be checked by SPF)
reply-to			Who replies go to
sender			Not needed, not often used
x-sender			As per sender, but might not be an email address

Full details:
http://people.dsv.su.se/~jpalme/ietf/ietf-mail-attributes.html
*/

/**
 * Attempt to send an e-mail to the specified recipient. The mail will be forwarding to the CC address specified in the options (if there is one, and if not specified not to cc).
 * The mail will be sent in dual HTML/text format, where the text is the unconverted comcode source: if a member does not read HTML mail, they may wish to fallback to reading that.
 *
 * @param  string			The subject of the mail in plain text
 * @param  LONG_TEXT		The message, as Comcode
 * @param  ?array			The destination (recipient) e-mail addresses [array of strings] (NULL: site staff address)
 * @param  ?mixed			The recipient name. Array or string. (NULL: site name)
 * @param  EMAIL			The from address (blank: site staff address)
 * @param  string			The from name (blank: site name)
 * @param  integer		The message priority (1=urgent, 3=normal, 5=low)
 * @range  1 5
 * @param  ?array			An list of attachments (each attachment being a map, path=>filename) (NULL: none)
 * @param  boolean		Whether to NOT CC to the CC address
 * @param  ?MEMBER		Convert comcode->tempcode as this member (a privilege thing: we don't want people being able to use admin rights by default!) (NULL: guest)
 * @param  boolean		Replace above with arbitrary admin
 * @param  boolean		HTML-only
 * @param  boolean		Whether to bypass queueing, because this code is running as a part of the queue management tools
 * @param  ID_TEXT		The template used to show the email
 * @return ?tempcode		A full page (not complete XHTML) piece of tempcode to output (NULL: it worked so no tempcode message)
 */
function mail_wrap($subject_line,$message_raw,$to_email=NULL,$to_name=NULL,$from_email='',$from_name='',$priority=3,$attachments=NULL,$no_cc=false,$as=NULL,$as_admin=false,$in_html=false,$coming_out_of_queue=false,$mail_template='MAIL')
{
	if (running_script('stress_test_loader')) return NULL;

	if (@$GLOBALS['SITE_INFO']['no_email_output']==='1') return NULL;

	global $EMAIL_ATTACHMENTS;
	$EMAIL_ATTACHMENTS=array();

	require_code('site');
	require_code('mime_types');

	if (is_null($as)) $as=$GLOBALS['FORUM_DRIVER']->get_guest_id();

	if (!$coming_out_of_queue)
	{
		$GLOBALS['SITE_DB']->query('DELETE FROM '.get_table_prefix().'logged_mail_messages WHERE m_date_and_time<'.strval(time()-60*60*24*14).' AND m_queued=0'); // Log it all for 2 weeks, then delete

		$through_queue=(get_option('mail_queue_debug')==='1') || ((get_option('mail_queue')==='1') && (cron_installed()));

		$GLOBALS['SITE_DB']->query_insert('logged_mail_messages',array(
			'm_subject'=>substr($subject_line,0,255),
			'm_message'=>$message_raw,
			'm_to_email'=>serialize($to_email),
			'm_to_name'=>serialize($to_name),
			'm_from_email'=>$from_email,
			'm_from_name'=>$from_name,
			'm_priority'=>$priority,
			'm_attachments'=>serialize($attachments),
			'm_no_cc'=>$no_cc?1:0,
			'm_as'=>$as,
			'm_as_admin'=>$as_admin?1:0,
			'm_in_html'=>$in_html?1:0,
			'm_date_and_time'=>time(),
			'm_member_id'=>get_member(),
			'm_url'=>get_self_url(true),
			'm_queued'=>((get_option('mail_queue_debug')==='1') || ((get_option('mail_queue')==='1') && (cron_installed())))?1:0,
			'm_template'=>$mail_template,
		),false,!$through_queue); // No errors if we don't NEED this to work

		if ($through_queue) return NULL;
	}

	if (count($attachments)==0) $attachments=NULL;

	global $SENDING_MAIL;
	if ($SENDING_MAIL) return NULL;
	$SENDING_MAIL=true;

	// To and from, and language
	$staff_address=get_option('staff_address');
	if (is_null($to_email)) $to_email=array($staff_address);
	$to_email_new=array();
	foreach ($to_email as $test_address)
	{
		if ($test_address!='') $to_email_new[]=$test_address;
	}
	$to_email=$to_email_new;
	if ($to_email==array())
	{
		$SENDING_MAIL=false;
		return NULL;
	}
	if ($to_email[0]==$staff_address)
	{
		$lang=get_site_default_lang();
	} else
	{
		$lang=user_lang();
		if (method_exists($GLOBALS['FORUM_DRIVER'],'get_member_from_email_address'))
		{
			$member_id=$GLOBALS['FORUM_DRIVER']->get_member_from_email_address($to_email[0]);
			if (!is_null($member_id))
			{
				$lang=get_lang($member_id);
			}
		}
	}
	if (is_null($to_name))
	{
		if ($to_email[0]==$staff_address)
		{
			$to_name=get_site_name();
		} else
		{
			$to_name='';
		}
	}
	if ($from_email=='') $from_email=get_option('staff_address');
	if ($from_name=='') $from_name=get_site_name();
	$from_email=str_replace("\r",'',$from_email);
	$from_email=str_replace("\n",'',$from_email);
	$from_name=str_replace("\r",'',$from_name);
	$from_name=str_replace("\n",'',$from_name);

	$theme=method_exists($GLOBALS['FORUM_DRIVER'],'get_theme')?$GLOBALS['FORUM_DRIVER']->get_theme():'default';
	if ($theme=='default') // Sucks, probably due to sending from Admin Zone...
	{
		$theme=$GLOBALS['FORUM_DRIVER']->get_theme(''); // ... So get theme of welcome zone
	}

	// Line termination is fiddly. It is safer to rely on sendmail supporting \n than undetectable-qmail/postfix-masquerading-as-sendmail not supporting the correct \r\n
	/*$sendmail_path=ini_get('sendmail_path');
	if ((strpos($sendmail_path,'qmail')!==false) || (strpos($sendmail_path,'sendmail')!==false))
		$line_term="\n";
	else
		$line_term="\r\n";
	*/
	if ((strtoupper(substr(PHP_OS,0,3))=='WIN') || (get_option('smtp_sockets_use')=='1'))
	{
		$line_term="\r\n";
	/*} elseif (strtoupper(substr(PHP_OS,0,3))=='MAC')
	{
		$line_term="\r";*/
	} else
	{
		$line_term="\n";
	}

	// We use the boundary to seperate message parts
	$_boundary=uniqid('ocPortal');
	$boundary=$_boundary.'_1';
	$boundary2=$_boundary.'_2';
	$boundary3=$_boundary.'_3';

	// Our subject
	$subject=do_template('MAIL_SUBJECT',array('_GUID'=>'44a57c666bb00f96723256e26aade9e5','SUBJECT_LINE'=>$subject_line),$lang,false,NULL,'.tpl','templates',$theme);
	$tightened_subject=$subject->evaluate($lang); // Note that this is slightly against spec, because characters aren't forced to be printable us-ascii. But it's better we allow this (which works in practice) than risk incompatibility via charset-base64 encoding.
	$tightened_subject=str_replace(chr(10),'',$tightened_subject);
	$tightened_subject=str_replace(chr(13),'',$tightened_subject);

	$regexp='#^[\x'.dechex(32).'-\x'.dechex(126).']*$#';
	if (preg_match($regexp,$tightened_subject)==0) $tightened_subject='=?'.do_lang('charset',NULL,NULL,NULL,$lang).'?B?'.base64_encode($tightened_subject)."?=";
	if (preg_match($regexp,$from_name)==0) $from_name='=?'.do_lang('charset',NULL,NULL,NULL,$lang).'?B?'.base64_encode($from_name)."?=";
	if (is_array($to_name))
	{
		foreach ($to_name as $i=>$_to_name)
		{
			if (preg_match($regexp,$_to_name)==0) $to_name[$i]='=?'.do_lang('charset',NULL,NULL,NULL,$lang).'?B?'.base64_encode($_to_name)."?=";
		}
	} else
	{
		if (preg_match($regexp,$to_name)==0) $to_name='=?'.do_lang('charset',NULL,NULL,NULL,$lang).'?B?'.base64_encode($to_name)."?=";
	}

	$simplify_when_can=true; // Used for testing. Not actually needed

	// Evaluate message. Needs doing early so we know if we have any headers
	$GLOBALS['NO_LINK_TITLES']=true;
	global $LAX_COMCODE;
	$temp=$LAX_COMCODE;
	$LAX_COMCODE=true;
	$html_content=comcode_to_tempcode($message_raw,$as,$as_admin);
	$LAX_COMCODE=$temp;
	$GLOBALS['NO_LINK_TITLES']=false;
	$attachments=array_merge(is_null($attachments)?array():$attachments,$EMAIL_ATTACHMENTS);

	// Headers
	$website_email=get_option('website_email');
	if ($website_email=='') $website_email=$from_email;
	if (get_value('use_true_from')!=='1')
	{
		$headers='From: "'.$from_name.'" <'.$website_email.'>'.$line_term;
	} else
	{
		$headers='From: "'.$from_name.'" <'.$from_email.'>'.$line_term;
	}
	$headers.='Reply-To: <'.$from_email.'>'.$line_term;
	$headers.='Return-Path: <'.$website_email.'>'.$line_term;
	$headers.='X-Sender: <'.$website_email.'>'.$line_term;
	$cc_address=$no_cc?'':get_option('cc_address');
	if (($cc_address!='') && (!in_array($cc_address,$to_email))) $headers.=((get_option('bcc')=='1')?'Bcc: <':'Cc: <').$cc_address.'>'.$line_term;
	$headers.='Message-ID: <'.$_boundary.'@'.get_domain().'>'.$line_term;
	$headers.='X-Priority: '.strval($priority).$line_term;
	$brand_name=get_value('rebrand_name');
	if (is_null($brand_name)) $brand_name='ocPortal';
	$headers.='X-Mailer: '.$brand_name.$line_term;
	$headers.='MIME-Version: 1.0'.$line_term;
	if ((!is_null($attachments)) || (!$simplify_when_can))
	{
		$headers.='Content-Type: multipart/mixed;'."\n\t".'boundary="'.$boundary.'"';
	} else
	{
		$headers.='Content-Type: multipart/alternative;'."\n\t".'boundary="'.$boundary2.'"';
	}
	$sending_message='';
	$sending_message.='This is a multi-part message in MIME format.'.$line_term.$line_term;
	if ((!is_null($attachments)) || (!$simplify_when_can))
	{
		$sending_message.='--'.$boundary.$line_term;
		$sending_message.='Content-Type: multipart/alternative;'."\n\t".'boundary="'.$boundary2.'"'.$line_term.$line_term.$line_term;
	}

	global $CID_IMG_ATTACHMENT;
	$CID_IMG_ATTACHMENT=array();

	// Message starts (actually: it is kind of in header form also as it uses mime multi-part)
	if (!$in_html)
	{
		$_html_content=$html_content->evaluate($lang);
		$_html_content=preg_replace('#(keep|for)_session=[\d\w]*#','filtered=1',$_html_content);
		$message_html=(strpos($_html_content,'<html')!==false)?make_string_tempcode($_html_content):do_template($mail_template,array('_GUID'=>'b23069c20202aa59b7450ebf8d49cde1','CSS'=>'{CSS}','LOGOURL'=>get_logo_url(''),/*'LOGOMAP'=>get_option('logo_map'),*/'LANG'=>$lang,'TITLE'=>$subject,'CONTENT'=>$_html_content),$lang,false,NULL,'.tpl','templates',$theme);
		$css=css_tempcode(true,true,$message_html->evaluate($lang),$theme);
		$_css=$css->evaluate($lang);
		if (get_option('allow_ext_images')!='1')
		{
			$_css=preg_replace_callback('#url\(["\']?(http://[^"]*)["\']?\)#U','_mail_css_rep_callback',$_css);
		}
		$html_evaluated=$message_html->evaluate($lang);
		$html_evaluated=str_replace('{CSS}',$_css,$html_evaluated);

		// Cleanup the Comcode a bit
		$message_plain=comcode_to_clean_text($message_raw);
	} else
	{
		$html_evaluated=$message_raw;
	}


	$base64_encode=(get_value('base64_emails')==='1'); // More robust, but more likely to be spam-blocked, and some servers can scramble it.

	// Plain version
	if (!$in_html)
	{
		$sending_message.='--'.$boundary2.$line_term;
		$sending_message.='Content-Type: text/plain; charset='.((preg_match($regexp,$message_plain)==0)?do_lang('charset',NULL,NULL,NULL,$lang):'us-ascii').$line_term; // '; name="message.txt"'.	Outlook doesn't like: makes it think it's an attachment
		if ($base64_encode)
		{
			$sending_message.='Content-Transfer-Encoding: base64'.$line_term.$line_term;
			$sending_message.=chunk_split(base64_encode(unixify_line_format($message_plain)).$line_term,76,$line_term);
		} else
		{
			$sending_message.='Content-Transfer-Encoding: 8bit'.$line_term.$line_term;
			$sending_message.=wordwrap(str_replace(chr(10),$line_term,unixify_line_format($message_plain)).$line_term,998,$line_term);
		}
	}

	// HTML version
	$sending_message.='--'.$boundary2.$line_term;
	$sending_message.='Content-Type: multipart/related;'."\n\t".'type="text/html";'."\n\t".'boundary="'.$boundary3.'"'.$line_term.$line_term.$line_term;
	$sending_message.='--'.$boundary3.$line_term;
	$sending_message.='Content-Type: text/html; charset='.((preg_match($regexp,$html_evaluated)==0)?do_lang('charset',NULL,NULL,NULL,$lang):'us-ascii').$line_term; // .'; name="message.html"'.	Outlook doesn't like: makes it think it's an attachment
	if (get_option('allow_ext_images')!='1')
	{
		$html_evaluated=preg_replace_callback('#<img\s([^>]*)src="(http://[^"]*)"#U','_mail_img_rep_callback',$html_evaluated);
		$matches=array();
		foreach (array('#<([^"<>]*\s)style="([^"]*)"#','#<style( [^<>]*)?'.'>(.*)</style>#Us') as $over)
		{
			$num_matches=preg_match_all($over,$html_evaluated,$matches);
			for ($i=0;$i<$num_matches;$i++)
			{
				$altered_inner=preg_replace_callback('#url\(["\']?(http://[^"]*)["\']?\)#U','_mail_css_rep_callback',$matches[2][$i]);
				if ($matches[2][$i]!=$altered_inner)
				{
					$altered_outer=str_replace($matches[2][$i],$altered_inner,$matches[0][$i]);
					$html_evaluated=str_replace($matches[0][$i],$altered_outer,$html_evaluated);
				}
			}
		}
	}

	if ($base64_encode)
	{
		$sending_message.='Content-Transfer-Encoding: base64'.$line_term.$line_term;
		$sending_message.=chunk_split(base64_encode(unixify_line_format($html_evaluated)).$line_term,76,$line_term);
	} else
	{
		$sending_message.='Content-Transfer-Encoding: 8bit'.$line_term.$line_term; // Requires RFC 1652
		$sending_message.=wordwrap(str_replace(chr(10),$line_term,unixify_line_format($html_evaluated)).$line_term,998,$line_term);
	}
	foreach ($CID_IMG_ATTACHMENT as $id=>$img)
	{
		$sending_message.='--'.$boundary3.$line_term;
		$file_path_stub=convert_url_to_path($img);
		$mime_type=get_mime_type(get_file_extension($img));
		$filename=basename($img);
		if (!is_null($file_path_stub))
		{
			$file_contents=@file_get_contents($file_path_stub);
		} else
		{
			$file_contents=mixed();
			$matches=array();
			if ((preg_match('#^'.preg_quote(find_script('attachment'),'#').'\?id=(\d+)&amp;thumb=(0|1)#',$img,$matches)!=0) && (strpos($img,'forum_db=1')===false))
			{
				$rows=$GLOBALS['SITE_DB']->query_select('attachments',array('*'),array('id'=>intval($matches[1])),'ORDER BY a_add_time DESC');
				if ((array_key_exists(0,$rows)) && (has_attachment_access($as,intval($matches[1]))))
				{
					$myrow=$rows[0];

					if ($matches[2]=='1')
					{
						$full=$myrow['a_thumb_url'];
					}
					else
					{
						$full=$myrow['a_url'];
					}

					if (url_is_local($full))
					{
						$_full=get_custom_file_base().'/'.rawurldecode($full);
						if (file_exists($_full))
						{
							$filename=$myrow['a_original_filename'];
							require_code('mime_types');
							$file_contents=file_get_contents($_full);
							$mime_type=get_mime_type(get_file_extension($filename));
						}
					}
				}
			}
			if ($file_contents===NULL)
			{
				$file_contents=http_download_file($img,NULL,false);
				if (is_null($file_contents)) continue;
				if (!is_null($GLOBALS['HTTP_DOWNLOAD_MIME_TYPE'])) $mime_type=$GLOBALS['HTTP_DOWNLOAD_MIME_TYPE'];
				if (!is_null($GLOBALS['HTTP_FILENAME'])) $filename=$GLOBALS['HTTP_FILENAME'];
			}
		}
		$sending_message.='Content-Type: '.str_replace("\r",'',str_replace("\n",'',$mime_type)).$line_term;
		$sending_message.='Content-ID: <'.$id.'>'.$line_term;
		$sending_message.='Content-Disposition: inline; filename="'.str_replace("\r",'',str_replace("\n",'',$filename)).'"'.$line_term;
		$sending_message.='Content-Transfer-Encoding: base64'.$line_term.$line_term;
		if (is_string($file_contents))
			$sending_message.=chunk_split(base64_encode($file_contents),76,$line_term);
	}
	$sending_message.=$line_term.'--'.$boundary3.'--'.$line_term.$line_term;

	$sending_message.=$line_term.'--'.$boundary2.'--'.$line_term.$line_term;

	// Attachments
	if (!is_null($attachments))
	{
		foreach ($attachments as $path=>$filename)
		{
			$sending_message.='--'.$boundary.$line_term;
			$sending_message.='Content-Type: '.get_mime_type(get_file_extension($filename)).$line_term; // .'; name="'.str_replace("\r",'',str_replace("\n",'',$filename)).'"'   http://www.imc.org/ietf-822/old-archive2/msg02121.html
			$sending_message.='Content-Transfer-Encoding: base64'.$line_term;
			$sending_message.='Content-Disposition: attachment; filename="'.str_replace("\r",'',str_replace("\n",'',$filename)).'"'.$line_term.$line_term;

			if (strpos($path,'://')===false)
			{
				if (!is_file($path)) continue;
				$contents=file_get_contents($path);
			} else
			{
				require_code('files');
				$contents=http_download_file($path,NULL,false);
				if (is_null($contents)) continue;
			}
			$sending_message.=chunk_split(base64_encode($contents),76,$line_term);
		}

		$sending_message.=$line_term.'--'.$boundary.'--'.$line_term;
	}

	// Support for SMTP sockets rather than PHP mail()
	$error=NULL;
	if (get_option('smtp_sockets_use')=='1')
	{
		$worked=false;

		$host=get_option('smtp_sockets_host');
		$port=intval(get_option('smtp_sockets_port'));

		$errno=0;
		$errstr='';
		foreach ($to_email as $i=>$to)
		{
			$socket=@fsockopen($host,$port,$errno,$errstr,30.0);
			if ($socket!==false)
			{
				$rcv=fgets($socket,1024);
				$base_url=parse_url(get_base_url());
				$domain=$base_url['host'];
				fwrite($socket,'HELO '.$domain."\r\n");
				$rcv=fgets($socket,1024);

				// Log in if necessary
				$username=get_option('smtp_sockets_username');
				$password=get_option('smtp_sockets_password');
				if ($username!='')
				{
					fwrite($socket,"AUTH LOGIN\r\n");
					$rcv=fgets($socket,1024);
					if (strtolower(substr($rcv,0,3))=='334')
					{
						fwrite($socket,base64_encode($username)."\r\n");
						$rcv=fgets($socket,1024);
						if ((strtolower(substr($rcv,0,3))=='235') || (strtolower(substr($rcv,0,3))=='334'))
						{
							fwrite($socket,base64_encode($password)."\r\n");
							$rcv=fgets($socket,1024);
							if (strtolower(substr($rcv,0,3))=='235')
							{
							} else $error=do_lang('MAIL_ERROR_CONNECT_PASSWORD').' ('.str_replace($password,'*',$rcv).')';
						} else $error=do_lang('MAIL_ERROR_CONNECT_USERNAME').' ('.$rcv.')';
					} else $error=do_lang('MAIL_ERROR_CONNECT_AUTH').' ('.$rcv.')';
				}

				if (is_null($error))
				{
					$smtp_from_address=get_option('smtp_from_address');
					if ($smtp_from_address=='') $smtp_from_address=$from_email;
					fwrite($socket,'MAIL FROM:<'.$website_email.">\r\n");
					$rcv=fgets($socket,1024);
					if ((strtolower(substr($rcv,0,3))=='250') || (strtolower(substr($rcv,0,3))=='251'))
					{
						$sent_one=false;
						fwrite($socket,"RCPT TO:<".$to_email[$i].">\r\n");
						$rcv=fgets($socket,1024);
						if ((strtolower(substr($rcv,0,3))!='250') && (strtolower(substr($rcv,0,3))!='251'))
							$error=do_lang('MAIL_ERROR_TO').' ('.$rcv.')'.' '.$to_email[$i];
						else $sent_one=true;
						if ($sent_one)
						{
							fwrite($socket,"DATA\r\n");
							$rcv=fgets($socket,1024);
							if (strtolower(substr($rcv,0,3))=='354')
							{
								$attractive_date=strftime('%d %B %Y  %H:%M:%S',time());

								if (count($to_email)==1)
								{
									if ($to_name==='')
									{
										fwrite($socket,'To: '.$to_email[$i]."\r\n");
									} else
									{
										fwrite($socket,'To: '.(is_array($to_name)?$to_name[$i]:$to_name).' <'.$to_email[$i].'>'."\r\n");
									}
								} else
								{
									fwrite($socket,'To: '.(is_array($to_name)?$to_name[$i]:$to_name)."\r\n");
								}
								fwrite($socket,'Subject: '.$tightened_subject."\r\n");
								fwrite($socket,'Date: '.$attractive_date."\r\n");
								fwrite($socket,$headers."\r\n");
								fwrite($socket,$sending_message);
								fwrite($socket,"\r\n.\r\n");
								$rcv=fgets($socket,1024);
								fwrite($socket,"QUIT\r\n");
								$rcv=fgets($socket,1024);
							} else $error=do_lang('MAIL_ERROR_DATA').' ('.$rcv.')';
						}
					} else $error=do_lang('MAIL_ERROR_FROM').' ('.$rcv.')';

					if (@fwrite($socket,"RSET\r\n")===false) // Cut out. At least one server does this
					{
						@fclose($socket);
						$socket=NULL;
					} else $rcv=fgets($socket,1024);
				}

				if (!is_null($socket)) fclose($socket);
				if (is_null($error)) $worked=true;
			} else
			{
				$error=do_lang('MAIL_ERROR_CONNECT',$host,strval($port));
			}
		}
	} else
	{
		$worked=false;
		foreach ($to_email as $i=>$to)
		{
			//exit($headers.chr(10).$sending_message);
			$GLOBALS['SUPRESS_ERROR_DEATH']=true;

			$additional='';
			if (get_option('enveloper_override')=='1') $additional='-f '.$website_email;
			if (($to_name==='') || (strtoupper(substr(PHP_OS,0,3))=='WIN'))
			{
				$to_line=$to;
			} else
			{
				$to_line='"'.(is_array($to_name)?$to_name[$i]:$to_name).'" <'.$to.'>';
			}
			//if (function_exists('mb_language')) mb_language('en');	Stop overridden mbstring mail function from messing and base64'ing stuff. Actually we don't need this as we make sure to pass through as headers with blank message, bypassing any filtering.
			$php_errormsg=mixed();
			if (ini_get('safe_mode')=='1')
			{
				$worked=mail($to_line,$tightened_subject,$sending_message,$headers);
			} else
			{
				$worked=mail($to_line,$tightened_subject,$sending_message,$headers,$additional);
			}
			if ((!$worked) && (isset($php_errormsg)) && (($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member())) || ($GLOBALS['IS_ACTUALLY_ADMIN'])))
				$error=$php_errormsg;
			$GLOBALS['SUPRESS_ERROR_DEATH']=false;
		}
	}

	if (!$worked)
	{
		$SENDING_MAIL=false;
		if (get_param_integer('keep_hide_mail_failure',0)==0)
		{
			require_code('site');
			attach_message(!is_null($error)?make_string_tempcode($error):do_lang_tempcode('MAIL_FAIL',escape_html(get_option('staff_address'))),'warn');
		} else
		{
			return warn_screen(get_screen_title('ERROR_OCCURRED'),do_lang_tempcode('MAIL_FAIL',escape_html(get_option('staff_address'))));
		}
	}

	$SENDING_MAIL=false;
	return NULL;
}

/**
 * Filter out any CSS selector blocks from the given CSS if they definitely do not affect the given (X)HTML.
 * Whilst this is a clever algorithm, it isn't so clever as to actually try and match each selector against a DOM tree. If any segment of a compound selector matches, match is assumed.
 *
 * @param  string			CSS
 * @param  string			(X)HTML context under which CSS is filtered
 * @return string			Filtered CSS
 */
function filter_css($css,$context)
{
	// Find out all our IDs
	$ids=array();
	$matches=array();
	$count=preg_match_all('#\sid=["\']([^"\']*)["\']#',$context,$matches);
	for ($i=0;$i<$count;$i++)
	{
		$ids[]=$matches[1][$i];
	}

	// Find out all our classes
	$classes=array();
	$count=preg_match_all('#\sclass=["\']([^"\']*)["\']#',$context,$matches);
	for ($i=0;$i<$count;$i++)
	{
		if ($matches[1][$i]=='') continue;
		$classes=array_merge($classes,preg_split('#\s+#',$matches[1][$i],-1,PREG_SPLIT_NO_EMPTY));
	}

	// Strip comments from CSS. This optimises, and also avoids us needing to do a sophisticated parse
	$css=preg_replace('#/\*.*\*/#Us','',$css);

	// Find and process each CSS selector block
	$stack=array();
	$css_new='';
	$last_pos=0;
	do
	{
		$pos1=strpos($css,'{',$last_pos);
		$pos2=strpos($css,'}',$last_pos);
		if (($pos1===false) && ($pos2===false)) break;

		if (($pos1===false) || (($pos2!==false) && ($pos2<$pos1)))
		{
			if (count($stack)!=0)
			{
				$start=array_pop($stack);
				if (count($stack)==0) // We've finished a top-level section
				{
					$real_start=strrpos(substr($css,0,$start),'}');
					$real_start=($real_start===false)?0:($real_start+1);
					$selectors=explode(',',trim(substr($css,$real_start,$start-$real_start)));
					$applies=false;
					foreach ($selectors as $selector)
					{
						$selector=trim($selector);

						if (strpos($selector,'@media print')!==false) break;

						// We let all tag-name selectors through if the tag exists in the document, unless they contain a class/ID specifier -- in which case we toe to the presence of that class/ID
						if ((strpos($selector,'.')===false) && (strpos($selector,'#')===false) && (preg_match('#(^|\s)(\w+)([\[\#\.:\s]|$)#',$selector,$matches)!=0))
						{
							$applies=true;
							break;
						}

						// ID selectors
						foreach ($ids as $id)
						{
							if (preg_match('#\#'.preg_quote($id,'#').'([\[\.:\s]|$)#',$selector)!=0)
							{
								$applies=true;
								break;
							}
						}

						// Class name selectors
						foreach ($classes as $class)
						{
							if (preg_match('#\.'.preg_quote($class,'#').'([\[\#:\s]|$)#',$selector)!=0)
							{
								$applies=true;
								break;
							}
						}
					}
					if ($applies)
					{
						$css_new.=trim(substr($css,$real_start,$pos2-$real_start+1)).chr(10).chr(10); // Append section
					}
				}
			} else
			{
				//return $css; // Parsing error, extra close
				// But actually it's best we let it continue on
			}
			$last_pos=$pos2+1;
		} else
		{
			array_push($stack,$pos1);
			$last_pos=$pos1+1;
		}
	}
	while (true);

	return $css_new;
}

/**
 * Entry script to process a form that needs to be emailed.
 */
function form_to_email_entry_script()
{
	require_lang('mail');
	form_to_email();

	global $PAGE_NAME_CACHE;
	$PAGE_NAME_CACHE='_form_to_email';
	$title=get_screen_title('MAIL_SENT');
	$text=do_lang_tempcode('MAIL_SENT_TEXT',escape_html(post_param('to_written_name',get_site_name())));
	$redirect=get_param('redirect',NULL);
	if (!is_null($redirect))
	{
		require_code('site2');
		$tpl=redirect_screen($title,$redirect,$text);
	} else
	{
		$tpl=do_template('INFORM_SCREEN',array('_GUID'=>'e577a4df79eefd9064c14240cc99e947','TITLE'=>$title,'TEXT'=>$text));
	}
	$echo=globalise($tpl,NULL,'',true);
	$echo->evaluate_echo();
}

/**
 * Send the posted form over email to the staff address.
 *
 * @param  ?string	The subject of the email (NULL: from posted subject parameter).
 * @param  string		The intro text to the mail.
 * @param  ?array		A map of fields to field titles to transmit. (NULL: all posted fields, except subject and email)
 * @param  ?string	Email address to send to (NULL: look from post environment / staff address).
 */
function form_to_email($subject=NULL,$intro='',$fields=NULL,$to_email=NULL)
{
	if (is_null($subject)) $subject=post_param('subject',get_site_name());
	if (is_null($fields))
	{
		$fields=array();
		foreach (array_diff(array_keys($_POST),array('MAX_FILE_SIZE','perform_validation','_validated','posting_ref_id','f_face','f_colour','f_size','x','y','name','subject','email','to_members_email','to_written_name','redirect','http_referer')) as $key)
		{
			$is_hidden=(strpos($key,'hour')!==false) || (strpos($key,'access_')!==false) || (strpos($key,'minute')!==false) || (strpos($key,'confirm')!==false) || (strpos($key,'pre_f_')!==false) || (strpos($key,'label_for__')!==false) || (strpos($key,'wysiwyg_version_of_')!==false) || (strpos($key,'is_wysiwyg')!==false) || (strpos($key,'require__')!==false) || (strpos($key,'tempcodecss__')!==false) || (strpos($key,'comcode__')!==false) || (strpos($key,'_parsed')!==false) || (preg_match('#^caption\d+$#',$key)!=0) || (preg_match('#^attachmenttype\d+$#',$key)!=0) || (substr($key,0,1)=='_') || (substr($key,0,9)=='hidFileID') || (substr($key,0,11)=='hidFileName');
			if ($is_hidden) continue;

			if (substr($key,0,1)!='_')
				$fields[$key]=post_param('label_for__'.$key,titleify($key));
		}
	}

	$message_raw=$intro;
	if ($message_raw!='') $message_raw.="\n\n------------\n\n";
	foreach ($fields as $field=>$field_title)
	{
		$field_val=post_param($field,NULL);
		if (!is_null($field_val))
			$message_raw.=$field_title.': '.$field_val."\n\n";
	}
	$from_email=trim(post_param('email',''));

	$to_name=mixed();
	$from_name=post_param('name',$GLOBALS['FORUM_DRIVER']->get_username(get_member()));
	if (is_null($to_email))
	{
		$to=post_param_integer('to_members_email',NULL);
		if (!is_null($to))
		{
			$to_email=$GLOBALS['FORUM_DRIVER']->get_member_email_address($to);
			$to_name=$GLOBALS['FORUM_DRIVER']->get_username($to);
		}
	}

	$attachments=array();
	require_code('uploads');
	is_swf_upload(true);
	foreach ($_FILES as $file)
	{
		$attachments[$file['tmp_name']]=$file['name'];
	}

	if (addon_installed('captcha'))
	{
		if (post_param_integer('_security',0)==1)
		{
			require_code('captcha');
			enforce_captcha();
		}
	}

	mail_wrap($subject,$message_raw,array($to_email),$to_name,$from_email,$from_name,3,$attachments);
}

