PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/e107_handlers/mail.php

https://github.com/CasperGemini/e107
PHP | 942 lines | 606 code | 72 blank | 264 comment | 109 complexity | f0bb803f3865f4ec02e4c600fc4651f3 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /*
  3. * e107 website system
  4. *
  5. * Copyright (C) 2008-2013 e107 Inc (e107.org)
  6. * Released under the terms and conditions of the
  7. * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
  8. *
  9. * Mail handler
  10. */
  11. /**
  12. *
  13. * @package e107
  14. * @subpackage e107_handlers
  15. *
  16. * Mailout handler - concerned with processing and sending a single email
  17. * Extends the PHPMailer class
  18. */
  19. /*
  20. TODO:
  21. 1. Mustn't include header in text section of emails
  22. 2. Option to wrap HTML in a standard header (up to <body>) and footer (from </body>)
  23. Maybe each template is an array with several parts - optional header and footer, use defaults if not defined
  24. header looks for the {STYLESHEET} variable
  25. If we do that, can have a single override file, plus a core file
  26. 3. mail (PHP method) - note that it has parameters for additional headers and other parameters
  27. 4. Check that language support works - PHPMailer defaults to English if other files not available
  28. - PHPMailer expects a 2-letter code - $this->SetLanguage(CORE_LC) - e.g. 'en', 'br'
  29. 5. Logging:
  30. - Use rolling log for errors - error string(s) available - sort out entry
  31. - Look at support of some other logging options
  32. 9. Make sure SMTPDebug can be set (TRUE/FALSE)
  33. 12. Check support for port number - ATM we just override for SSL. Looks as if phpmailer can take it from end of server link.
  34. 18. Note object iteration - may be useful for dump of object state
  35. 19. Consider overriding error handler
  36. 20. Look at using new prefs structure
  37. 21. Should we always send an ID?
  38. 22. Force singleton so all mail sending flow controlled a bit (but not where parameters overridden in constructor)
  39. Tested so far (with PHP4 version)
  40. ------------
  41. SMTP send (standard)
  42. Return receipts
  43. text or mixed
  44. replyto
  45. priority field
  46. TLS to googlemail (use TLS)
  47. Notes if problems
  48. -----------------
  49. 1. Attachment adding call had dirname() added round path.
  50. 2. There are legacy and new methods for generating a multi-part body (HTML + plain text). Only the new method handles inline images.
  51. - Currently uses the new method (which is part of phpmailer)
  52. General notes
  53. -------------
  54. 1. Can specify a comma-separated list of smtp servers - presumably all require the same login credentials
  55. 2. qmail can be used (if available) by selecting sendmail, and setting the sendmail path to that for qmail instead
  56. 3. phpmailer does trim() on passed parameters where needed - so we don't need to.
  57. 4. phpmailer has its own list of MIME types.
  58. 5. Attachments - note method available for passing string attachments
  59. - AddStringAttachment($string,$filename,$encoding,$type)
  60. 6. Several email address-related methods can accept two comma-separated strings, one for addresses and one for related names
  61. 7. Its possible to send a text-only email by passing an array of parameters including 'send_html' = FALSE
  62. 8. For bulk emailing, must call the 'allSent()' method when complete to ensure SMTP mailer is properly closed.
  63. 9. For sending through googlemail (and presumably gmail), use TLS
  64. 10. Note that the 'add_html_header' option adds only the DOCTYPE bits - not the <head>....</head> section
  65. Possible Enhancements
  66. ---------------------
  67. 1. Support other fields:
  68. ContentType
  69. Encoding - ???. Defaults to 8-bit
  70. Preferences used:
  71. $pref['mailer'] - connection type - SMTP, sendmail etc
  72. $pref['mail_options'] - NEW - general mailing options
  73. textonly - if true, defaults to plain text emails
  74. hostname=text - used in Message ID and received headers, and default Helo string. (Otherwise server-related default used)
  75. $pref['mail_log_options'] - NEW. Logging options (also used in mailout_process). Comma-separated list of values
  76. 1 - logenable - numeric value 0..3 controlling logging to a text file
  77. 2 - add_email - if '1', the detail of the email is logged as well
  78. $pref['smtp_server'] |
  79. $pref['smtp_username'] | Server details. USed for POP3 server if POP before SMTP authorisation
  80. $pref['smtp_password'] |
  81. $pref['smtp_keepalive'] - deprecated in favour of option - flag
  82. $pref['smtp_pop3auth'] - deprecated in favour of option - POP before SMTP authorisation flag
  83. $pref['smtp_options'] - NEW - comma separated list:
  84. keepalive - If active, bulk email send keeps the SMTP connection open, closing it every $pref['mail_pause'] emails
  85. useVERP - formats return path to facilitate bounce processing
  86. secure=[TLS|SSL] - enable secure authorisation by TLS or SSL
  87. pop3auth - enable POP before SMTP authorisation
  88. helo=text - Alternative Helo string
  89. $pref['sendmail'] - path to sendmail
  90. $pref['mail_pause'] - number of emails to send before pause
  91. $pref['mail_pausetime'] - time to pause
  92. $pref['mail_bounce_email'] - 'reply to' address
  93. $pref['mail_bounce_pop3']
  94. $pref['mail_bounce_user']
  95. $pref['mail_bounce_pass']
  96. Usage
  97. =====
  98. 1. Create new object of the correct class
  99. 2. Set up everything - to/from/email etc
  100. 3. Call create_connection()
  101. 4. Call send_mail()
  102. +----------------------------------------------------------------------------+
  103. */
  104. if (!defined('e107_INIT')) { exit; }
  105. //define('MAIL_DEBUG',TRUE);
  106. //define('LOG_CALLER', TRUE);
  107. require_once(e_HANDLER.'phpmailer/class.phpmailer.php');
  108. // Directory for log (if enabled)
  109. define('MAIL_LOG_PATH',e_LOG);
  110. class e107Email extends PHPMailer
  111. {
  112. private $general_opts = array();
  113. private $logEnable = 0; // 0 = log disabled, 1 = 'dry run' (debug and log, no send). 2 = 'log all' (send, and log result)
  114. private $logHandle = FALSE; // Save handle of log file if opened
  115. private $localUseVerp = FALSE; // Use our own variable - PHPMailer one doesn't work with all mailers
  116. private $save_bouncepath = ''; // Used with VERP
  117. private $add_email = 0; // 1 includes email detail in log (if logging enabled, of course)
  118. private $allow_html = 1; // Flag for HTML conversion - '1' = default, FALSE = disable, TRUE = force.
  119. private $add_HTML_header = FALSE; // If TRUE, inserts a standard HTML header at the front of the HTML part of the email (set FALSE for BC)
  120. private $SendCount = 0; // Keep track of how many emails sent since last SMTP open/connect (used for SMTP KeepAlive)
  121. private $TotalSent = 0; // Info might be of interest
  122. private $TotalErrors = 0; // Count errors in sending emails
  123. private $pause_amount = 10; // Number of emails to send before pausing/resetting (or closing if SMTPkeepAlive set)
  124. private $pause_time = 1; // Time to pause after sending a block of emails
  125. public $legacyBody = FALSE; // TRUE enables legacy conversion of plain text body to HTML in HTML emails
  126. /**
  127. * Constructor sets up all the global options, and sensible defaults - it should be the only place the prefs are accessed
  128. *
  129. * @var array $overrides - array of values which override mail-related prefs. Key is the same as the corresponding pref.
  130. * - second batch of keys can preset values configurable through the arraySet() method
  131. * @return none
  132. */
  133. public function __construct($overrides = FALSE)
  134. {
  135. parent::__construct(FALSE); // Parent constructor - no exceptions for now
  136. $e107 = e107::getInstance();
  137. $pref = e107::pref('core');
  138. $tp = e107::getParser();
  139. $this->CharSet = 'utf-8';
  140. $this->SetLanguage(CORE_LC);
  141. if (($overrides === FALSE) || !is_array($overrides))
  142. {
  143. $overrides = array();
  144. }
  145. foreach (array('mailer', 'smtp_server', 'smtp_username', 'smtp_password', 'sendmail', 'siteadminemail', 'siteadmin') as $k)
  146. {
  147. if (!isset($overrides[$k])) $overrides[$k] = $pref[$k];
  148. }
  149. $this->pause_amount = varset($pref['mail_pause'], 10);
  150. $this->pause_time = varset($pref['mail_pausetime'], 1);
  151. if (varsettrue($pref['mail_options'])) $this->general_opts = explode(',',$pref['mail_options'],'');
  152. if (defined('MAIL_DEBUG')) echo 'Mail_options: '.$pref['mail_options'].' Count: '.count($this->general_opts).'<br />';
  153. foreach ($this->general_opts as $k => $v)
  154. {
  155. $v = trim($v);
  156. $this->general_opts[$k] = $v;
  157. if (strpos($v,'hostname') === 0)
  158. {
  159. list(,$this->HostName) = explode('=',$v);
  160. if (defined('MAIL_DEBUG')) echo "Host name set to: {$this->HostName}<br />";
  161. }
  162. }
  163. list($this->logEnable,$this->add_email) = explode(',',varset($pref['mail_log_options'],'0,0'));
  164. switch ($overrides['mailer'])
  165. {
  166. case 'smtp' :
  167. $smtp_options = array();
  168. $temp_opts = explode(',',varset($pref['smtp_options'],''));
  169. if (varsettrue($overrides ['smtp_pop3auth'])) $temp_opts[] = 'pop3auth'; // Legacy option - remove later
  170. if (varsettrue($pref['smtp_keepalive'])) $temp_opts[] = 'keepalive'; // Legacy option - remove later
  171. foreach ($temp_opts as $k=>$v)
  172. {
  173. if (strpos($v,'=') !== FALSE)
  174. {
  175. list($v,$k) = explode('=',$v,2);
  176. $smtp_options[trim($v)] = trim($k);
  177. }
  178. else
  179. {
  180. $smtp_options[trim($v)] = TRUE; // Simple on/off option
  181. }
  182. }
  183. unset($temp_opts);
  184. $this->IsSMTP(); // Enable SMTP functions
  185. if (varsettrue($smtp_options['helo'])) $this->Helo = $smtp_options['helo'];
  186. if (isset($smtp_options['pop3auth'])) // We've made sure this is set
  187. { // Need POP-before-SMTP authorisation
  188. require_once(e_HANDLER.'phpmailer/class.pop3.php');
  189. $pop = new POP3();
  190. $pop->Authorise($overrides['smtp_server'], 110, 30, $overrides['smtp_username'], $overrides['smtp_password'], 1);
  191. }
  192. $this->Mailer = 'smtp';
  193. $this->localUseVerp = isset($smtp_options['useVERP']);
  194. if (isset($smtp_options['secure']))
  195. {
  196. switch ($smtp_options['secure'])
  197. {
  198. case 'TLS' :
  199. $this->SMTPSecure = 'tls';
  200. $this->Port = 465; // Can also use port 587, and maybe even 25
  201. break;
  202. case 'SSL' :
  203. $this->SMTPSecure = 'ssl';
  204. $this->Port = 465;
  205. break;
  206. default :
  207. if (defined('MAIL_DEBUG')) echo "Invalid option: {$smtp_options['secure']}<br />";
  208. }
  209. }
  210. $this->SMTPKeepAlive = varset($smtp_options['keepalive'],FALSE); // ***** Control this
  211. $this->Host = $overrides['smtp_server'];
  212. if($overrides['smtp_username'] && $overrides['smtp_password'])
  213. {
  214. $this->SMTPAuth = (!isset($smtp_options['pop3auth']));
  215. $this->Username = $overrides['smtp_username'];
  216. $this->Password = $overrides['smtp_password'];
  217. }
  218. break;
  219. case 'sendmail' :
  220. $this->Mailer = 'sendmail';
  221. $this->Sendmail = ($overrides['sendmail']) ? $overrides['sendmail'] : '/usr/sbin/sendmail -t -i -r '.varsettrue($pref['replyto_email'],$overrides['siteadminemail']);
  222. break;
  223. case 'php' :
  224. $this->Mailer = 'mail';
  225. break;
  226. }
  227. if (varsettrue($pref['mail_bounce_email'])) $this->Sender = $pref['mail_bounce_email'];
  228. $this->FromName = $tp->toHTML(varsettrue($pref['replyto_name'],$overrides['siteadmin']),'','RAWTEXT');
  229. $this->From = $tp->toHTML(varsettrue($pref['replyto_email'],$overrides['siteadminemail']),'','RAWTEXT');
  230. $this->WordWrap = 76; // Set a sensible default
  231. // Now look for any overrides - slightly cumbersome way of doing it, but does give control over what can be set from here
  232. // Options are those accepted by the arraySet() method.
  233. foreach (array('SMTPDebug', 'email_subject', 'email_sender_email', 'email_sender_name', 'email_replyto', 'send_html',
  234. 'add_html_header', 'email_attach', 'email_copy_to', 'email_bcopy_to',
  235. 'bouncepath', 'returnreceipt', 'email_inline_images', 'email_priority', 'extra_header', 'wordwrap', 'split') as $opt)
  236. {
  237. if (isset($overrides[$opt]))
  238. {
  239. $this->arraySet(array($opt => $overrides[$opt]));
  240. }
  241. }
  242. }
  243. /**
  244. * Set log level
  245. * @param int $level 0|1|2
  246. * @param int $emailDetails 0|1
  247. * @return e107Email
  248. */
  249. public function logEnable($level, $emailDetails = null)
  250. {
  251. $this->logEnable = (int) $level;
  252. if(null !== $this->add_email)
  253. {
  254. $this->add_email = (int) $emailDetails;
  255. }
  256. return $this;
  257. }
  258. /**
  259. * Disable log completely
  260. * @return e107Email
  261. */
  262. public function logDisable()
  263. {
  264. $this->logEnable = 0;
  265. $this->add_email = 0;
  266. return $this;
  267. }
  268. /**
  269. * Format 'to' address and name
  270. *
  271. * @param string $email - email address of recipient
  272. * @param string $to - name of recipient
  273. * @return string in form: Fred Bloggs<fred.bloggs@somewhere.com>
  274. */
  275. public function makePrintableAddress($email,$to)
  276. {
  277. $to = trim($to);
  278. $email = trim($email);
  279. return $to.' <'.$email.'>';
  280. }
  281. /**
  282. * Log functions - write to a log file
  283. * Each entry logged to a separate line
  284. *
  285. * @return none
  286. */
  287. protected function openLog($logInfo = TRUE)
  288. {
  289. if ($this->logEnable && ($this->logHandle === FALSE))
  290. {
  291. $logFileName = MAIL_LOG_PATH.'mailoutlog.txt';
  292. $this->logHandle = fopen($logFileName, 'a'); // Always append to file
  293. }
  294. if ($this->logHandle !== FALSE)
  295. {
  296. fwrite($this->logHandle,"\n\n=====".date('H:i:s y.m.d')."----------------------------------------------------------------=====\r\n");
  297. if ($logInfo)
  298. {
  299. fwrite($this->logHandle,' Mailer opened by '.USERNAME." - ID: {$this->MessageID}. Subject: {$this->Subject} Log action: {$this->logEnable}\r\n");
  300. if ($this->add_email)
  301. {
  302. fwrite($this->logHandle, 'From: '.$this->From.' ('.$this->FromName.")\r\n");
  303. fwrite($this->logHandle, 'Sender: '.$this->Sender."\r\n");
  304. fwrite($this->logHandle, 'Subject: '.$this->Subject."\r\n");
  305. // Following are private variables ATM
  306. // fwrite($this->logHandle, 'CC: '.$email_info['copy_to']."\r\n");
  307. // fwrite($this->logHandle, 'BCC: '.$email_info['bcopy_to']."\r\n");
  308. // fwrite($this->logHandle, 'Attach: '.$attach."\r\n");
  309. fwrite($this->logHandle, 'Body: '.$this->Body."\r\n");
  310. fwrite($this->logHandle,"-----------------------------------------------------------\r\n");
  311. }
  312. }
  313. if (defined('LOG_CALLER'))
  314. {
  315. $temp = debug_backtrace();
  316. foreach ($temp as $t)
  317. {
  318. if (!isset($t['class']) || ($t['class'] != 'e107Email'))
  319. {
  320. fwrite($this->logHandle, print_a($t,TRUE)."\r\n"); // Found the caller
  321. break;
  322. }
  323. }
  324. }
  325. }
  326. }
  327. /**
  328. * Add a line to log file - time/date is prepended, and CRLF is appended
  329. *
  330. * @param string $text - line to add
  331. * @return none
  332. */
  333. protected function logLine($text)
  334. {
  335. if ($this->logEnable && ($this->logHandle > 0))
  336. {
  337. fwrite($this->logHandle,date('H:i:s y.m.d').' - '.$text."\r\n");
  338. }
  339. }
  340. /**
  341. * Close log
  342. */
  343. protected function closeLog()
  344. {
  345. if ($this->logEnable && ($this->logHandle > 0))
  346. {
  347. fclose($this->logHandle);
  348. }
  349. }
  350. /**
  351. * Add a list of addresses to one of the address lists.
  352. * @param string $list - 'to', 'replyto', 'cc', 'bcc'
  353. * @param string $addresses - comma separated
  354. * @param string $names - either a single name (used for all addresses) or a comma-separated list corresponding to the address list
  355. * If the name field for an entry is blank, or there are not enough entries, the address is substituted
  356. * @return TRUE if list accepted, FALSE if invalid list name
  357. */
  358. public function AddAddressList($list = 'to',$addresses,$names = '')
  359. {
  360. $list = trim(strtolower($list));
  361. $tmp = explode(',',$addresses);
  362. if (strpos($names,',') === FALSE)
  363. {
  364. $names = array_fill(0,count($tmp),$names); // Same value for all addresses
  365. }
  366. else
  367. {
  368. $names = explode(',',$names);
  369. }
  370. foreach($tmp as $k => $adr)
  371. {
  372. $to_name = ($names[$k]) ? $names[$k] : $adr;
  373. switch ($list)
  374. {
  375. case 'to' :
  376. $this->AddAddress($adr, $to_name);
  377. break;
  378. case 'replyto' :
  379. $this->AddReplyTo($adr, $to_name);
  380. break;
  381. case 'cc' :
  382. if($this->Mailer == 'mail')
  383. {
  384. $this->AddCustomHeader('Cc: '.$adr);
  385. }
  386. else
  387. {
  388. $this->AddCC($adr, $to_name);
  389. }
  390. break;
  391. case 'bcc' :
  392. if($this->Mailer == 'mail')
  393. {
  394. $this->AddCustomHeader('Bcc: '.$adr);
  395. }
  396. else
  397. {
  398. $this->AddBCC($adr, $to_name);
  399. }
  400. break;
  401. default :
  402. return FALSE;
  403. }
  404. }
  405. return TRUE;
  406. }
  407. /**
  408. * Create email body, primarily using the inbuilt functionality of phpmailer
  409. *
  410. * @param boolean|int $want_HTML determines whether an HTML part of the email is created. 1 uses default setting for HTML part. Set TRUE to enable, FALSE to disable
  411. * @param boolean $add_HTML_header - if TRUE, a standard HTML header is added to the front of the HTML part
  412. *
  413. * @return none
  414. */
  415. public function makeBody($message,$want_HTML = 1, $add_HTML_header = FALSE)
  416. {
  417. switch (varset($this->general_opts['textonly'],'off'))
  418. {
  419. case 'pref' : // Disable HTML as default
  420. if ($want_HTML == 1) $want_HTML = FALSE;
  421. break;
  422. case 'force' : // Always disable HTML
  423. $want_HTML = FALSE;
  424. break;
  425. }
  426. if ($want_HTML !== FALSE)
  427. {
  428. if (defined('MAIL_DEBUG')) echo "Generating multipart email<br />";
  429. if ($add_HTML_header)
  430. {
  431. $message = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n
  432. <html xmlns='http://www.w3.org/1999/xhtml' >\n".$message;
  433. }
  434. if ($this->legacyBody && !preg_match('/<(font|br|a|img|b)/i', $message)) // Assume html if it includes one of these tags
  435. { // Otherwise assume its a plain text message which needs some conversion to render in HTML
  436. $message = htmlspecialchars($message,ENT_QUOTES,$this->CharSet);
  437. $message = preg_replace('%(http|ftp|https)(://\S+)%', '<a href="\1\2">\1\2</a>', $message);
  438. $message = preg_replace('/([[:space:]()[{}])(www.[-a-zA-Z0-9@:%_\+.~#?&\/\/=]+)/i', '\\1<a href="http://\\2">\\2</a>', $message);
  439. $message = preg_replace('/([_\.0-9a-z-]+@([0-9a-z][0-9a-z-]+\.)+[a-z]{2,3})/i', '<a href="mailto:\\1">\\1</a>', $message);
  440. $message = str_replace("\r\n","\n",$message); // Handle alternative newline characters
  441. $message = str_replace("\n\r","\n",$message); // Handle alternative newline characters
  442. $message = str_replace("\r","\n",$message); // Handle alternative newline characters
  443. $message = str_replace("\n", "<br />\n", $message);
  444. }
  445. $this->MsgHTML($message); // Theoretically this should do everything, including handling of inline images.
  446. }
  447. else
  448. { // generate the plain text as the sole part of the email
  449. if (defined('MAIL_DEBUG')) echo "Generating plain text email<br />";
  450. if (strpos($message,'</style>') !== FALSE)
  451. {
  452. $text = strstr($message,'</style>');
  453. }
  454. else
  455. {
  456. $text = $message;
  457. }
  458. $text = str_replace('<br />', "\n", $text);
  459. $text = strip_tags(str_replace('<br>', "\n", $text));
  460. // TODO: strip bbcodes here
  461. $this->Body = $text;
  462. $this->AltBody = ''; // Single part email
  463. }
  464. }
  465. /**
  466. * Add attachments to the current email - either a single one as a string, or an array
  467. * Always sent in base64 encoding
  468. *
  469. * @param string|array $attachments - single attachment name as a string, or any number as an array
  470. *
  471. * @return none
  472. */
  473. public function attach($attachments)
  474. {
  475. if (!$attachments) return;
  476. if (!is_array($attachments)) $attachments = array($attachments);
  477. foreach($attachments as $attach)
  478. {
  479. $tempName = basename($attach);
  480. if(is_readable($attach) && $tempName)
  481. { // First parameter is complete path + filename; second parameter is 'name' of file to send
  482. $ext = pathinfo($attach, PATHINFO_EXTENSION);
  483. $this->AddAttachment($attach, $tempName,'base64',$this->_mime_types($ext));
  484. }
  485. }
  486. }
  487. /**
  488. * Add inline images (should usually be handled automatically by PHPMailer)
  489. *
  490. * @param string $inline - comma separated list of file names
  491. */
  492. function addInlineImages($inline)
  493. {
  494. if(!$inline) return;
  495. $tmp = explode(',',$inline);
  496. foreach($tmp as $inline_img)
  497. {
  498. if(is_readable($inline_img) && !is_dir($inline_img))
  499. {
  500. $ext = pathinfo($inline_img, PATHINFO_EXTENSION);
  501. $this->AddEmbeddedImage($inline_img, md5($inline_img), basename($inline_img),'base64',$this->_mime_types($ext));
  502. }
  503. }
  504. }
  505. /**
  506. * Sets one or more parameters from an array. See @see{sendEmail()} for list of parameters
  507. * Where parameter not present, doesn't change it - so can repeatedly call this function for bulk mailing, or to build up the list
  508. * (Note that there is no requirement to use this method for everything; parameters can be set by mixing this method with individual setting)
  509. *
  510. * @param array $paramlist - list of parameters to set/change. Key is parameter name. @see{sendEmail()} for list of parameters
  511. *
  512. * @return int zero if no errors detected
  513. */
  514. public function arraySet($paramlist)
  515. {
  516. if (isset($paramlist['SMTPDebug'])) $this->SMTPDebug = $paramlist['SMTPDebug']; // 'FALSE' is a valid value!
  517. if (varsettrue($paramlist['email_subject'])) $this->Subject = $paramlist['email_subject'];
  518. if (varsettrue($paramlist['email_sender_email'])) $this->From = $paramlist['email_sender_email'];
  519. if (varsettrue($paramlist['email_sender_name'])) $this->FromName = $paramlist['email_sender_name'];
  520. if (varsettrue($paramlist['email_replyto'])) $this->AddAddressList('replyto',$paramlist['email_replyto'],varsettrue($paramlist['email_replytonames'],''));
  521. if (isset($paramlist['send_html'])) $this->allow_html = $paramlist['send_html']; // 'FALSE' is a valid value!
  522. if (isset($paramlist['add_html_header'])) $this->add_HTML_header = $paramlist['add_html_header']; // 'FALSE' is a valid value!
  523. if (varsettrue($paramlist['email_body'])) $this->makeBody($paramlist['email_body'], $this->allow_html, $this->add_HTML_header);
  524. if (varsettrue($paramlist['email_attach'])) $this->attach($paramlist['email_attach']);
  525. if (varsettrue($paramlist['email_copy_to'])) $this->AddAddressList('cc',$paramlist['email_copy_to'],varsettrue($paramlist['email_cc_names'],''));
  526. if (varsettrue($paramlist['email_bcopy_to'])) $this->AddAddressList('bcc',$paramlist['email_bcopy_to'],varsettrue($paramlist['email_bcc_names'],''));
  527. if (varsettrue($paramlist['bouncepath']))
  528. {
  529. $this->Sender = $paramlist['bouncepath']; // Bounce path
  530. $this->save_bouncepath = $paramlist['bouncepath']; // Bounce path
  531. }
  532. if (varsettrue($paramlist['returnreceipt'])) $this->ConfirmReadingTo = $paramlist['returnreceipt'];
  533. if (varsettrue($paramlist['email_inline_images'])) $this->addInlineImages($paramlist['email_inline_images']);
  534. if (varsettrue($paramlist['email_priority'])) $this->Priority = $paramlist['email_priority'];
  535. if (varsettrue($paramlist['e107_header'])) $this->AddCustomHeader("X-e107-id: {$paramlist['e107_header']}");
  536. if (varsettrue($paramlist['extra_header']))
  537. {
  538. if (is_array($paramlist['extra_header']))
  539. {
  540. foreach($paramlist['extra_header'] as $eh)
  541. {
  542. $this->addCustomHeader($eh);
  543. }
  544. }
  545. else
  546. {
  547. $this->addCustomHeader($paramlist['extra_header']);
  548. }
  549. }
  550. if (varset($paramlist['wordwrap'])) $this->WordWrap = $paramlist['wordwrap'];
  551. if (varsettrue($paramlist['split'])) $this->SingleTo = ($paramlist['split'] != FALSE);
  552. return 0; // No error
  553. }
  554. /**
  555. Send an email where the bulk of the data is passed in an array. Returns 0 on success.
  556. (Even if the array is null, because everything previously set up, this is the preferred entry point)
  557. Where parameter not present in the array, doesn't get changed - useful for bulk mailing
  558. If doing bulk mailing with repetitive calls, set $bulkmail parameter true, and must call allSent() when completed
  559. Some of these parameters have been made compatible with the array calculated by render_email() in signup.php
  560. Possible array parameters:
  561. $eml['email_subject']
  562. $eml['email_sender_email'] - 'From' email address
  563. $eml['email_sender_name'] - 'From' name
  564. $eml['email_replyto'] - Optional 'reply to' field
  565. $eml['email_replytonames'] - Name(s) corresponding to 'reply to' field - only used if 'replyto' used
  566. $eml['send_html'] - if TRUE, includes HTML part in messages (only those added after this flag)
  567. $eml['add_html_header'] - if TRUE, adds the 2-line DOCTYPE declaration to the front of the HTML part (but doesn't add <head>...</head>)
  568. $eml['email_body'] - message body. May be HTML or text. Added according to the current state of the HTML enable flag
  569. $eml['email_attach'] - string if one file, array of filenames if one or more.
  570. $eml['email_copy_to'] - comma-separated list of cc addresses.
  571. $eml['email_cc_names'] - comma-separated list of cc names. Optional, used only if $eml['email_copy_to'] specified
  572. $eml['email_bcopy_to'] - comma-separated list
  573. $eml['email_bcc_names'] - comma-separated list of bcc names. Optional, used only if $eml['email_copy_to'] specified
  574. $eml['bouncepath'] - Sender field (used for bounces)
  575. $eml['returnreceipt'] - email address for notification of receipt (reading)
  576. $eml['email_inline_images'] - array of files for inline images
  577. $eml['priority'] - Email priority (1 = High, 3 = Normal, 5 = low)
  578. $eml['e107_header'] - Adds specific 'X-e107-id:' header
  579. $eml['extra_header'] - additional headers (format is name: value
  580. $eml['wordwrap'] - Set wordwrap value
  581. $eml['split'] - If true, sends an individual email to each recipient
  582. * @param string $send_to - recipient email address
  583. * @param string $to_name - recipient name
  584. * @param array $eml - optional array of additional parameters (see above)
  585. * @param boolean $bulkmail - set TRUE if this email is one of a bulk send; FALSE if an isolated email
  586. *
  587. * @return boolean|string - TRUE if success, error message if failure
  588. */
  589. public function sendEmail($send_to, $to_name, $eml = '', $bulkmail = FALSE)
  590. {
  591. if (count($eml))
  592. { // Set parameters from list
  593. $ret = $this->arraySet($eml);
  594. if ($ret) return $ret;
  595. }
  596. if ($bulkmail && $this->localUseVerp && $this->save_bouncepath && (strpos($this->save_bouncepath,'@') !== FALSE))
  597. {
  598. // Format where sender is owner@origin, target is user@domain is: owner+user=domain@origin
  599. list($our_sender,$our_domain) = explode('@', $this->save_bouncepath,2);
  600. if ($our_sender && $our_domain)
  601. {
  602. $this->Sender = $our_sender.'+'.str_replace($send_to,'@','=').'@'.$our_domain;
  603. }
  604. }
  605. $this->AddAddressList('to',$send_to,$to_name);
  606. $this->openLog(); // Delay log open until now, so all parameters set up
  607. $result = TRUE; // Temporary 'success' flag
  608. $this->SendCount++;
  609. if (($this->logEnable == 0) || ($this->logEnable == 2))
  610. {
  611. $result = $this->Send(); // Actually send email
  612. if (!$bulkmail && !$this->SMTPKeepAlive && ($this->Mailer == 'smtp')) $this->SmtpClose();
  613. }
  614. else
  615. { // Debug
  616. $result = TRUE;
  617. //print_a($this);
  618. if (($this->logEnable == 3) && (($this->SendCount % 7) == 4)) $result = FALSE; // Fail one email in 7 for testing
  619. }
  620. $this->TotalSent++;
  621. if (($this->pause_amount > 0) && ($this->SendCount >= $this->pause_amount))
  622. {
  623. if ($this->SMTPKeepAlive && ($this->Mailer == 'smtp')) $this->SmtpClose();
  624. sleep($this->pause_time);
  625. $this->SendCount = 0;
  626. }
  627. $this->logLine("Send to {$to_name} at {$send_to} Mail-ID={$this->MessageID} - ".($result ? 'Success' : 'Fail'));
  628. $this->ClearAddresses(); // In case we send another email
  629. $this->ClearCustomHeaders();
  630. if ($result)
  631. {
  632. $this->closeLog();
  633. return TRUE;
  634. }
  635. $this->logLine('Error info: '.$this->ErrorInfo);
  636. // Error sending email
  637. $e107 = e107::getInstance();
  638. $e107->admin_log->e_log_event(3,debug_backtrace(),"MAIL","Send Failed",$this->ErrorInfo,FALSE,LOG_TO_ROLLING);
  639. $this->TotalErrors++;
  640. $this->closeLog();
  641. return $this->ErrorInfo;
  642. }
  643. /**
  644. * Called after a bulk mailing completed, to tidy up nicely
  645. *
  646. * @return none
  647. */
  648. public function allSent()
  649. {
  650. if ($this->SMTPKeepAlive && ($this->Mailer == 'smtp') && ($this->SendCount > 0))
  651. {
  652. $this->SmtpClose();
  653. $this->SendCount = 0;
  654. }
  655. }
  656. /**
  657. * Evaluates the message and returns modifications for inline images and backgrounds
  658. * Also creates an alternative plain text part (unless $this->AltBody already non-empty)
  659. * Modification of standard PHPMailer function (which it overrides)
  660. * @access public
  661. *
  662. * @param string $message - the mail body to send
  663. * @basedir string - optional 'root part' of paths specified in email - prepended as necessary
  664. *
  665. * @return string none (message saved ready to send)
  666. */
  667. public function MsgHTML($message, $basedir = '')
  668. {
  669. preg_match_all("/(src|background)=([\"\'])(.*)\\2/Ui", $message, $images); // Modified to accept single quotes as well
  670. if(isset($images[3]))
  671. {
  672. foreach($images[3] as $i => $url)
  673. {
  674. // do not change urls for absolute images (thanks to corvuscorax)
  675. if (!preg_match('#^[A-z]+://#',$url))
  676. {
  677. $delim = $images[2][$i]; // Will be single or double quote
  678. $filename = basename($url);
  679. $directory = dirname($url);
  680. if ($directory == '.') $directory='';
  681. if (strpos($directory, e_HTTP) === 0)
  682. {
  683. $directory = substr(SERVERBASE, 0, -1).$directory; // Convert to absolute server reference
  684. $basedir = '';
  685. }
  686. //echo "CID file {$filename} in {$directory}. Base = ".SERVERBASE."< BaseDir = {$basedir}<br />";
  687. $cid = 'cid:' . md5($filename);
  688. $ext = pathinfo($filename, PATHINFO_EXTENSION);
  689. $mimeType = self::_mime_types($ext);
  690. if ( (strlen($basedir) > 1) && (substr($basedir,-1) != '/') && (substr($basedir,-1) != '\\')) { $basedir .= '/'; }
  691. if ( strlen($directory) > 1 && substr($directory,-1) != '/' && substr($directory,-1) != '\\') { $directory .= '/'; }
  692. //echo "Add image: {$basedir}|{$directory}|{$filename}<br />";
  693. if ( $this->AddEmbeddedImage($basedir.$directory.$filename, md5($filename), $filename, 'base64',$mimeType) )
  694. {
  695. // $images[1][$i] contains 'src' or 'background'
  696. $message = preg_replace("/".$images[1][$i]."=".$delim.preg_quote($url, '/').$delim."/Ui", $images[1][$i]."=".$delim.$cid.$delim, $message);
  697. }
  698. else
  699. {
  700. if (defined('MAIL_DEBUG')) echo "Add embedded image {$url} failed<br />";
  701. }
  702. }
  703. }
  704. }
  705. $this->IsHTML(true);
  706. $this->Body = $message;
  707. //print_a($message);
  708. $textMsg = str_replace(array('<br />', '<br>'), "\n", $message); // Modified to make sure newlines carried through
  709. $textMsg = preg_replace('#^.*?<body.*?>#', '', $textMsg); // Knock off everything up to and including the body statement (if present)
  710. $textMsg = preg_replace('#</body.*?>.*$#', '', $textMsg); // Knock off everything after and including the </body> (if present)
  711. $textMsg = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s','',$textMsg)));
  712. if (!empty($textMsg) && empty($this->AltBody))
  713. {
  714. $this->AltBody = html_entity_decode($textMsg);
  715. }
  716. if (empty($this->AltBody))
  717. {
  718. $this->AltBody = 'To view this email message, enable HTML!' . "\n\n";
  719. }
  720. }
  721. } // End of e107Mailer class
  722. //-----------------------------
  723. // Exception handler
  724. //-----------------------------
  725. // Overrides the phpmailer handler
  726. // For now just work the same as the phpmailer handler - maybe add features to log to rolling log or something later
  727. // Could throw an e107Exception
  728. class e107MailerException extends phpmailerException
  729. {
  730. public function errorMessage()
  731. {
  732. return parent::errorMsg();
  733. }
  734. }
  735. //--------------------------------------
  736. // Generic e107 Exception handler
  737. //--------------------------------------
  738. // Overrides the default handler - start of a more general handler
  739. class e107Exception extends Exception
  740. {
  741. public function __construct($message = '', $code = 0)
  742. {
  743. parent::__construct($message, $code);
  744. $e107 = e107::getInstance();
  745. $e107->admin_log->e_log_event(10,
  746. $this->getFile().'|@'.$this->getLine(),
  747. 'EXCEPT',
  748. $this->getCode().':'.$this->getMessage(),
  749. $this->getTraceAsString(),
  750. FALSE,
  751. LOG_TO_ROLLING);
  752. }
  753. }
  754. //-----------------------------------------------------
  755. // Function call to send an email
  756. //-----------------------------------------------------
  757. /**
  758. * Function call to send an email
  759. *
  760. * Deprecated function
  761. *
  762. * Preferred method is to instantiate an e107MailManager object, and use the sendEmails() method, which also allows templates.
  763. *
  764. * see also sendTemplated() where non-default formating is required
  765. *
  766. * Note that plain text emails are converted to HTML, and also sent with a text part
  767. *
  768. * @param string $send_to - email address of recipient
  769. * @param string $subject
  770. * @param string $message
  771. * @param string $to_name
  772. * @param string $send_from - sender email address. (Defaults to the sitewide 'replyto' name and email if set, otherwise site admins details)
  773. * @param string $from_name - sender name. If $send_from is empty, defaults to the sitewide 'replyto' name and email if set, otherwise site admins details
  774. * @param string $attachments - comma-separated list of attachments
  775. * @param string $Cc - comma-separated list of 'copy to' email addresses
  776. * @param string $Bcc - comma-separated list of 'blind copy to' email addresses
  777. * @param string $returnpath - Sets 'reply to' email address
  778. * @param boolean $returnreceipt - TRUE to request receipt
  779. * @param string $inline - comma separated list of images to send inline
  780. *
  781. * @return boolean TRUE if send successfully (NOT an indication of receipt!), FALSE if error
  782. */
  783. function sendemail($send_to, $subject, $message, $to_name='', $send_from='', $from_name='', $attachments='', $Cc='', $Bcc='', $returnpath='', $returnreceipt='',$inline ='')
  784. {
  785. global $mailheader_e107id;
  786. $overrides = array();
  787. // TODO: Find a way of doing this which doesn't use a global (or just ditch sendemail() )
  788. // Use defaults from email template?
  789. // ----- Mail pref. template override for parked domains, site mirrors or dynamic values
  790. global $EMAIL_OVERRIDES;
  791. if (isset($EMAIL_OVERRIDES) && is_array($EMAIL_OVERRIDES))
  792. {
  793. $overrides = &$EMAIL_OVERRIDES; // These can override many of the email-related prefs
  794. if (isset($EMAIL_OVERRIDES['bouncepath'])) $returnpath = $EMAIL_OVERRIDES['bouncepath'];
  795. if (isset($EMAIL_OVERRIDES['returnreceipt'])) $returnreceipt = $EMAIL_OVERRIDES['returnreceipt'];
  796. }
  797. // Create a mailer object of the correct type (which auto-fills in sending method, server details)
  798. $mail = new e107Email($overrides);
  799. if (varsettrue($mailheader_e107id)) $mail->AddCustomHeader("X-e107-id: {$mailheader_e107id}");
  800. $mail->legacyBody = TRUE; // Need to handle plain text email conversion to HTML
  801. $mail->makeBody($message); // Add body, with conversion if required
  802. if($Cc) $mail->AddAddressList('cc', $Cc);
  803. if ($Bcc) $mail->AddAddressList('bcc', $Bcc);
  804. if (trim($send_from))
  805. {
  806. $mail->SetFrom($send_from, $from_name); // These have already been defaulted to sitewide options, so no need to set again if blank
  807. }
  808. $mail->Subject = $subject;
  809. $mail->attach($attachments);
  810. // Add embedded images (should be auto-handled now)
  811. if ($inline) $mail->addInlineImages($inline);
  812. // Passed parameter overrides any system default for bounce - but should this be 'ReplyTo' address instead?
  813. // if (varsettrue($returnpath)) $mail->Sender = $AddReplyToAddresses($returnpath,'');
  814. if (varsettrue($returnpath)) $mail->Sender = $returnpath;
  815. if (varsettrue($returnreceipt)) $mail->ConfirmReadingTo($returnreceipt);
  816. if ($mail->sendEmail($send_to,$to_name) === TRUE)
  817. { // Success
  818. return TRUE;
  819. }
  820. // Error info already logged
  821. return FALSE;
  822. }
  823. ?>