smtp.mail.inc 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. <?php
  2. /**
  3. * @file
  4. * The code processing mail in the smtp module.
  5. *
  6. */
  7. /**
  8. * Modify the drupal mail system to use smtp when sending emails.
  9. * Include the option to choose between plain text or HTML
  10. */
  11. class SmtpMailSystem implements MailSystemInterface {
  12. protected $AllowHtml;
  13. /**
  14. * Concatenate and wrap the e-mail body for either
  15. * plain-text or HTML emails.
  16. *
  17. * @param $message
  18. * A message array, as described in hook_mail_alter().
  19. *
  20. * @return
  21. * The formatted $message.
  22. */
  23. public function format(array $message) {
  24. $this->AllowHtml = variable_get('smtp_allowhtml', 0);
  25. // Join the body array into one string.
  26. $message['body'] = implode("\n\n", $message['body']);
  27. if ($this->AllowHtml == 0) {
  28. // Convert any HTML to plain-text.
  29. $message['body'] = drupal_html_to_text($message['body']);
  30. // Wrap the mail body for sending.
  31. $message['body'] = drupal_wrap_mail($message['body']);
  32. }
  33. return $message;
  34. }
  35. /**
  36. * Send the e-mail message.
  37. *
  38. * @see drupal_mail()
  39. *
  40. * @param $message
  41. * A message array, as described in hook_mail_alter().
  42. * @return
  43. * TRUE if the mail was successfully accepted, otherwise FALSE.
  44. */
  45. public function mail(array $message) {
  46. if (variable_get('smtp_queue', FALSE)
  47. && (!isset($message['params']['skip_queue']) || !$message['params']['skip_queue'])) {
  48. smtp_send_queue($message);
  49. if (variable_get('smtp_debugging', SMTP_LOGGING_ERRORS) == SMTP_LOGGING_ALL) {
  50. watchdog('smtp', 'Queue sending mail to: @to', array('@to' => $message['to']));
  51. }
  52. return TRUE;
  53. }
  54. else {
  55. return $this->mailWithoutQueue($message);
  56. }
  57. }
  58. public function mailWithoutQueue(array $message) {
  59. $to = $message['to'];
  60. $from = $message['from'];
  61. $from_name = isset($message['from_name']) ? $message['from_name'] : FALSE;
  62. $body = $message['body'];
  63. $headers = $message['headers'];
  64. $subject = $message['subject'];
  65. // Optionally reroute all emails to a single address.
  66. $reroute_address = variable_get('smtp_reroute_address', '');
  67. if (!empty($reroute_address)) {
  68. $to = $reroute_address;
  69. // Remove any CC and BCC headers that might have been set.
  70. unset($headers['cc']);
  71. unset($headers['bcc']);
  72. }
  73. // Create a new PHPMailer object - autoloaded from registry.
  74. $mailer = new PHPMailer();
  75. $logging = variable_get('smtp_debugging', SMTP_LOGGING_ERRORS);
  76. // Turn on debugging, if requested.
  77. if ($logging == SMTP_LOGGING_ALL && user_access('administer smtp module')) {
  78. $mailer->SMTPDebug = TRUE;
  79. }
  80. // Set the From Name.
  81. $from_name = FALSE;
  82. $from_comp = $this->_get_components($from);
  83. // If the From Name has been passed to this function, use that first.
  84. if($from_comp['name'] != '') {
  85. $from_name = $from_comp['name'];
  86. }
  87. // If we don't have a From Name set, try to get the name from i18n, in the case
  88. // that it has been translated. The name is set according to the language
  89. // of the email being sent.
  90. if (empty($from_name)) {
  91. if (function_exists('i18n_variable_get')) {
  92. // The 'language' value may be stored as an object.
  93. $langcode = $message['language'];
  94. if (is_object($langcode)) {
  95. $langcode = $langcode->language;
  96. }
  97. if (i18n_variable_get('smtp_fromname', $langcode, '') != '') {
  98. $from_name = i18n_variable_get('smtp_fromname', $langcode, '');
  99. }
  100. // If value is not defined in settings, use site_name.
  101. else {
  102. $from_name = i18n_variable_get('site_name', $langcode, '');
  103. }
  104. }
  105. // If the i18n module is not enabled, get the From name as a
  106. // normal variable.
  107. else {
  108. if (variable_get('smtp_fromname', '') != '') {
  109. $from_name = variable_get('smtp_fromname', '');
  110. }
  111. // If value is not defined in settings, use site_name.
  112. else {
  113. $from_name = variable_get('site_name', '');
  114. }
  115. }
  116. }
  117. if (variable_get('smtp_client_hostname', '') != '') {
  118. $mailer->Hostname = variable_get('smtp_client_hostname', '');
  119. }
  120. if (variable_get('smtp_client_helo', '') != '') {
  121. $mailer->Helo = variable_get('smtp_client_helo', '');
  122. }
  123. if (variable_get('smtp_client_hostname', '') != '') {
  124. $mailer->Hostname = variable_get('smtp_client_hostname', '');
  125. }
  126. if (variable_get('smtp_client_helo', '') != '') {
  127. $mailer->Helo = variable_get('smtp_client_helo', '');
  128. }
  129. // To pass DMARC, the email sender and headers should match.
  130. // Set the Reply-To header to the original 'From' email address.
  131. $from = $from_comp['email'];
  132. if (!isset($headers['Reply-To']) || empty($headers['Reply-To'])) {
  133. $headers['Reply-To'] = $from;
  134. }
  135. // If the 'From' address has been set in the SMTP module admin settings, use that as the sender.
  136. $smtp_from_var = variable_get('smtp_from', FALSE);
  137. if(($smtp_from_var) && valid_email_address($smtp_from_var)) {
  138. $from = $smtp_from_var;
  139. }
  140. // If we still don't have a 'From' address, fallback to the site's email.
  141. if(empty($from)) {
  142. $from = variable_get('site_mail', '');
  143. }
  144. if ($from == NULL || $from == '') {
  145. // If from e-mail address is blank, use smtp_from config option.
  146. if (($from = variable_get('smtp_from', '')) == '') {
  147. // If smtp_from config option is blank, use site_email.
  148. if (($from = variable_get('site_mail', '')) == '') {
  149. drupal_set_message(t('There is no submitted from address.'), 'error');
  150. if ($logging) {
  151. watchdog('smtp', 'There is no submitted from address.', array(), WATCHDOG_ERROR);
  152. }
  153. return FALSE;
  154. }
  155. }
  156. }
  157. if (!valid_email_address($from)) {
  158. drupal_set_message(t('The submitted from address (@from) is not valid.', array('@from' => $from_comp['email'])), 'error');
  159. if ($logging) {
  160. watchdog('smtp', 'The submitted from address (@from) is not valid.', array('@from' => $from_comp['email']), WATCHDOG_ERROR);
  161. }
  162. return FALSE;
  163. }
  164. // Set headers to match sender to prevent e-mail from looking like spam
  165. $headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $from;
  166. // Create the list of 'To:' recipients.
  167. $torecipients = explode(',', $to);
  168. foreach ($torecipients as $torecipient) {
  169. $to_comp = $this->_get_components($torecipient);
  170. $mailer->AddAddress($to_comp['email'], $to_comp['name']);
  171. }
  172. // Parse the headers of the message and set the PHPMailer object's settings
  173. // accordingly.
  174. foreach ($headers as $key => $value) {
  175. //watchdog('error', 'Key: ' . $key . ' Value: ' . $value);
  176. switch (drupal_strtolower($key)) {
  177. case 'from':
  178. // set the correct From headers
  179. $mailer->setFrom($from, $from_name);
  180. break;
  181. case 'content-type':
  182. // Parse several values on the Content-type header, storing them in an array like
  183. // key=value -> $vars['key']='value'
  184. $vars = explode(';', $value);
  185. foreach ($vars as $i => $var) {
  186. if ($cut = strpos($var, '=')) {
  187. $new_var = trim(drupal_strtolower(drupal_substr($var, $cut + 1)));
  188. $new_key = trim(drupal_substr($var, 0, $cut));
  189. unset($vars[$i]);
  190. $vars[$new_key] = $new_var;
  191. }
  192. }
  193. // Set the charset based on the provided value, otherwise set it to UTF-8 (which is Drupals internal default).
  194. $mailer->CharSet = isset($vars['charset']) ? $vars['charset'] : 'UTF-8';
  195. // If $vars is empty then set an empty value at index 0 to avoid a PHP warning in the next statement
  196. $vars[0] = isset($vars[0])?$vars[0]:'';
  197. switch ($vars[0]) {
  198. case 'text/plain':
  199. // The message includes only a plain text part.
  200. $mailer->IsHTML(FALSE);
  201. $content_type = 'text/plain';
  202. break;
  203. case 'text/html':
  204. // The message includes only an HTML part.
  205. $mailer->IsHTML(TRUE);
  206. $content_type = 'text/html';
  207. break;
  208. case 'multipart/related':
  209. // Get the boundary ID from the Content-Type header.
  210. $boundary = $this->_get_substring($value, 'boundary', '"', '"');
  211. // The message includes an HTML part w/inline attachments.
  212. $mailer->ContentType = $content_type = 'multipart/related; boundary="' . $boundary . '"';
  213. break;
  214. case 'multipart/alternative':
  215. // The message includes both a plain text and an HTML part.
  216. $mailer->ContentType = $content_type = 'multipart/alternative';
  217. // Get the boundary ID from the Content-Type header.
  218. $boundary = $this->_get_substring($value, 'boundary', '"', '"');
  219. break;
  220. case 'multipart/mixed':
  221. // The message includes one or more attachments.
  222. $mailer->ContentType = $content_type = 'multipart/mixed';
  223. // Get the boundary ID from the Content-Type header.
  224. $boundary = $this->_get_substring($value, 'boundary', '"', '"');
  225. break;
  226. default:
  227. // Everything else is unsuppored by PHPMailer.
  228. drupal_set_message(t('The %header of your message is not supported by PHPMailer and will be sent as text/plain instead.', array('%header' => "Content-Type: $value")), 'error');
  229. if ($logging) {
  230. watchdog('smtp', 'The %header of your message is not supported by PHPMailer and will be sent as text/plain instead.', array('%header' => "Content-Type: $value"), WATCHDOG_ERROR);
  231. }
  232. // Force the Content-Type to be text/plain.
  233. $mailer->IsHTML(FALSE);
  234. $content_type = 'text/plain';
  235. }
  236. break;
  237. case 'reply-to':
  238. $replyto_comp = $this->_get_components($value);
  239. $mailer->AddReplyTo($replyto_comp['email'], $replyto_comp['name']);
  240. break;
  241. case 'content-transfer-encoding':
  242. $mailer->Encoding = $value;
  243. break;
  244. case 'return-path':
  245. $returnpath_comp = $this->_get_components($value);
  246. $mailer->Sender = $returnpath_comp['email'];
  247. break;
  248. case 'mime-version':
  249. case 'x-mailer':
  250. // Let PHPMailer specify these.
  251. break;
  252. case 'errors-to':
  253. $mailer->AddCustomHeader('Errors-To: ' . $value);
  254. break;
  255. case 'cc':
  256. $ccrecipients = explode(',', $value);
  257. foreach ($ccrecipients as $ccrecipient) {
  258. $cc_comp = $this->_get_components($ccrecipient);
  259. $mailer->AddCC($cc_comp['email'], $cc_comp['name']);
  260. }
  261. break;
  262. case 'bcc':
  263. $bccrecipients = explode(',', $value);
  264. foreach ($bccrecipients as $bccrecipient) {
  265. $bcc_comp = $this->_get_components($bccrecipient);
  266. $mailer->AddBCC($bcc_comp['email'], $bcc_comp['name']);
  267. }
  268. break;
  269. case 'message-id':
  270. $mailer->MessageID = $value;
  271. break;
  272. default:
  273. // The header key is not special - add it as is.
  274. $mailer->AddCustomHeader($key . ': ' . $value);
  275. }
  276. }
  277. /**
  278. * TODO
  279. * Need to figure out the following.
  280. *
  281. * Add one last header item, but not if it has already been added.
  282. * $errors_to = FALSE;
  283. * foreach ($mailer->CustomHeader as $custom_header) {
  284. * if ($custom_header[0] = '') {
  285. * $errors_to = TRUE;
  286. * }
  287. * }
  288. * if ($errors_to) {
  289. * $mailer->AddCustomHeader('Errors-To: '. $from);
  290. * }
  291. */
  292. // Add the message's subject.
  293. $mailer->Subject = $subject;
  294. // Processes the message's body.
  295. switch ($content_type) {
  296. case 'multipart/related':
  297. $mailer->Body = $body;
  298. // TODO: Figure out if there is anything more to handling this type.
  299. break;
  300. case 'multipart/alternative':
  301. // Split the body based on the boundary ID.
  302. $body_parts = $this->_boundary_split($body, $boundary);
  303. foreach ($body_parts as $body_part) {
  304. // If plain/text within the body part, add it to $mailer->AltBody.
  305. if (strpos($body_part, 'text/plain')) {
  306. // Clean up the text.
  307. $body_part = trim($this->_remove_headers(trim($body_part)));
  308. // Include it as part of the mail object.
  309. $mailer->AltBody = $body_part;
  310. }
  311. // If plain/html within the body part, add it to $mailer->Body.
  312. elseif (strpos($body_part, 'text/html')) {
  313. // Clean up the text.
  314. $body_part = trim($this->_remove_headers(trim($body_part)));
  315. // Include it as part of the mail object.
  316. $mailer->Body = $body_part;
  317. }
  318. }
  319. break;
  320. case 'multipart/mixed':
  321. // Split the body based on the boundary ID.
  322. $body_parts = $this->_boundary_split($body, $boundary);
  323. // Determine if there is an HTML part for when adding the plain text part.
  324. $text_plain = FALSE;
  325. $text_html = FALSE;
  326. foreach ($body_parts as $body_part) {
  327. if (strpos($body_part, 'text/plain')) {
  328. $text_plain = TRUE;
  329. }
  330. if (strpos($body_part, 'text/html')) {
  331. $text_html = TRUE;
  332. }
  333. }
  334. foreach ($body_parts as $body_part) {
  335. // If test/plain within the body part, add it to either
  336. // $mailer->AltBody or $mailer->Body, depending on whether there is
  337. // also a text/html part ot not.
  338. if (strpos($body_part, 'multipart/alternative')) {
  339. // Get boundary ID from the Content-Type header.
  340. $boundary2 = $this->_get_substring($body_part, 'boundary', '"', '"');
  341. // Clean up the text.
  342. $body_part = trim($this->_remove_headers(trim($body_part)));
  343. // Split the body based on the boundary ID.
  344. $body_parts2 = $this->_boundary_split($body_part, $boundary2);
  345. foreach ($body_parts2 as $body_part2) {
  346. // If plain/text within the body part, add it to $mailer->AltBody.
  347. if (strpos($body_part2, 'text/plain')) {
  348. // Clean up the text.
  349. $body_part2 = trim($this->_remove_headers(trim($body_part2)));
  350. // Include it as part of the mail object.
  351. $mailer->AltBody = $body_part2;
  352. $mailer->ContentType = 'multipart/mixed';
  353. }
  354. // If plain/html within the body part, add it to $mailer->Body.
  355. elseif (strpos($body_part2, 'text/html')) {
  356. // Get the encoding.
  357. $body_part2_encoding = trim($this->_get_substring($body_part2, 'Content-Transfer-Encoding', ':', "\n"));
  358. // Clean up the text.
  359. $body_part2 = trim($this->_remove_headers(trim($body_part2)));
  360. // Check whether the encoding is base64, and if so, decode it.
  361. if (drupal_strtolower($body_part2_encoding) == 'base64') {
  362. // Include it as part of the mail object.
  363. $mailer->Body = base64_decode($body_part2);
  364. // Ensure the whole message is recoded in the base64 format.
  365. $mailer->Encoding = 'base64';
  366. }
  367. else {
  368. // Include it as part of the mail object.
  369. $mailer->Body = $body_part2;
  370. }
  371. $mailer->ContentType = 'multipart/mixed';
  372. }
  373. }
  374. }
  375. // If text/plain within the body part, add it to $mailer->Body.
  376. elseif (strpos($body_part, 'text/plain')) {
  377. // Clean up the text.
  378. $body_part = trim($this->_remove_headers(trim($body_part)));
  379. if ($text_html) {
  380. $mailer->AltBody = $body_part;
  381. $mailer->IsHTML(TRUE);
  382. $mailer->ContentType = 'multipart/mixed';
  383. }
  384. else {
  385. $mailer->Body = $body_part;
  386. $mailer->IsHTML(FALSE);
  387. $mailer->ContentType = 'multipart/mixed';
  388. }
  389. }
  390. // If text/html within the body part, add it to $mailer->Body.
  391. elseif (strpos($body_part, 'text/html')) {
  392. // Clean up the text.
  393. $body_part = trim($this->_remove_headers(trim($body_part)));
  394. // Include it as part of the mail object.
  395. $mailer->Body = $body_part;
  396. $mailer->IsHTML(TRUE);
  397. $mailer->ContentType = 'multipart/mixed';
  398. }
  399. // Add the attachment.
  400. elseif (strpos($body_part, 'Content-Disposition: attachment;') && !isset($message['params']['attachments'])) {
  401. $file_path = $this->_get_substring($body_part, 'filename=', '"', '"');
  402. $file_name = $this->_get_substring($body_part, ' name=', '"', '"');
  403. $file_encoding = $this->_get_substring($body_part, 'Content-Transfer-Encoding', ' ', "\n");
  404. $file_type = $this->_get_substring($body_part, 'Content-Type', ' ', ';');
  405. if (file_exists($file_path)) {
  406. if (!$mailer->AddAttachment($file_path, $file_name, $file_encoding, $file_type)) {
  407. drupal_set_message(t('Attahment could not be found or accessed.'));
  408. }
  409. }
  410. else {
  411. // Clean up the text.
  412. $body_part = trim($this->_remove_headers(trim($body_part)));
  413. if (drupal_strtolower($file_encoding) == 'base64') {
  414. $attachment = base64_decode($body_part);
  415. }
  416. elseif (drupal_strtolower($file_encoding) == 'quoted-printable') {
  417. $attachment = quoted_printable_decode($body_part);
  418. }
  419. else {
  420. $attachment = $body_part;
  421. }
  422. $attachment_new_filename = drupal_tempnam('temporary://', 'smtp');
  423. $file_path = file_save_data($attachment, $attachment_new_filename, FILE_EXISTS_REPLACE);
  424. $real_path = drupal_realpath($file_path->uri);
  425. if (!$mailer->AddAttachment($real_path, $file_name)) {
  426. drupal_set_message(t('Attachment could not be found or accessed.'));
  427. }
  428. }
  429. }
  430. }
  431. break;
  432. default:
  433. $mailer->Body = $body;
  434. break;
  435. }
  436. // Process mimemail attachments, which are prepared in mimemail_mail().
  437. if (isset($message['params']['attachments'])) {
  438. foreach ($message['params']['attachments'] as $attachment) {
  439. // Allow file objects, handled as mimemail_html_body() does.
  440. if (is_object($attachment)) {
  441. $file = $attachment;
  442. $attachment = array();
  443. $attchmentArray = array('filecontent', 'filename', 'filemime');
  444. if (isset($file->filepath)) {
  445. $attchmentArray = array('filepath', 'filename', 'filemime');
  446. }
  447. foreach ($attchmentArray as $key) {
  448. if (isset($file->{$key})) {
  449. $attachment[$key] = $file->{$key};
  450. }
  451. }
  452. if (isset($file->uri)) {
  453. $attachment['filepath'] = $file->uri;
  454. }
  455. }
  456. if (isset($attachment['filecontent'])) {
  457. $mailer->AddStringAttachment($attachment['filecontent'], $attachment['filename'], 'base64', $attachment['filemime']);
  458. }
  459. if (!isset($attachment['filepath']) && isset($attachment['uri'])) {
  460. $attachment['filepath'] = $attachment['uri'];
  461. }
  462. if (isset($attachment['filepath'])) {
  463. $filename = isset($attachment['filename']) ? $attachment['filename'] : basename($attachment['filepath']);
  464. $filemime = isset($attachment['filemime']) ? $attachment['filemime'] : file_get_mimetype($attachment['filepath']);
  465. $mailer->AddAttachment($attachment['filepath'], $filename, 'base64', $filemime);
  466. }
  467. }
  468. }
  469. // Process inline images.
  470. if (!empty($message['params']['images'])) {
  471. foreach ($message['params']['images'] as $image) {
  472. if (!empty($image['filepath']) && !empty($image['cid'])) {
  473. if (file_exists($image['filepath'])) {
  474. $image_extension = pathinfo($image['filepath'], PATHINFO_EXTENSION);
  475. $image_mime_type = PHPMailer::_mime_types($image_extension);
  476. if (empty($image['name'])) {
  477. $image_name = pathinfo($image['filepath'], PATHINFO_FILENAME);
  478. }
  479. else {
  480. $image_name = $image['name'];
  481. }
  482. $mailer->AddEmbeddedImage($image['filepath'], $image['cid'], $image_name, 'base64', $image_mime_type);
  483. }
  484. }
  485. }
  486. }
  487. // Set the authentication settings.
  488. $username = variable_get('smtp_username', '');
  489. $password = variable_get('smtp_password', '');
  490. // If username and password are given, use SMTP authentication.
  491. if ($username != '' && $password != '') {
  492. $mailer->SMTPAuth = TRUE;
  493. $mailer->Username = $username;
  494. $mailer->Password = $password;
  495. }
  496. // Set the protocol prefix for the smtp host.
  497. switch (variable_get('smtp_protocol', 'standard')) {
  498. case 'ssl':
  499. $mailer->SMTPSecure = 'ssl';
  500. break;
  501. case 'tls':
  502. $mailer->SMTPSecure = 'tls';
  503. break;
  504. default:
  505. $mailer->SMTPSecure = '';
  506. }
  507. // Set SSL stream context options
  508. if (variable_get('smtp_protocol', 'standard') != 'standard') {
  509. $smtp_allow_self_signed = array('allow_self_signed' => variable_get('smtp_allow_self_signed', FALSE));
  510. if (version_compare(phpversion(), '5.6.0', '>=')) {
  511. $smtp_verify_peer = array('verify_peer' => variable_get('smtp_verify_peer', TRUE));
  512. $smtp_verify_peer_name = array('verify_peer_name' => variable_get('smtp_verify_peer_name', TRUE));
  513. $mailer->SMTPOptions = array(
  514. 'ssl' => array_merge($smtp_verify_peer, $smtp_verify_peer_name, $smtp_allow_self_signed),
  515. );
  516. }
  517. else {
  518. $smtp_verify_peer = array('verify_peer' => variable_get('smtp_verify_peer', FALSE));
  519. $mailer->SMTPOptions = array(
  520. 'ssl' => array_merge($smtp_verify_peer, $smtp_allow_self_signed),
  521. );
  522. }
  523. }
  524. // Set other connection settings.
  525. $mailer->Host = variable_get('smtp_host', '') . ';' . variable_get('smtp_hostbackup', '');
  526. $mailer->Port = variable_get('smtp_port', '25');
  527. $mailer->Mailer = 'smtp';
  528. // Integration with the Maillog module.
  529. if (module_exists('maillog')) {
  530. if (variable_get('maillog_log', TRUE)) {
  531. $record = new stdClass;
  532. // In case the subject/from/to is already encoded, decode with
  533. // mime_header_decode.
  534. $record->header_message_id = isset($mailer->MessageID) ? $mailer->MessageID : NULL;
  535. $record->subject = drupal_substr(mime_header_decode($mailer->Subject), 0, 255);
  536. $record->header_from = $from;
  537. $record->header_to = $to;
  538. $record->header_reply_to = isset($headers['Reply-To']) ? $headers['Reply-To'] : '';
  539. $record->header_all = serialize($headers);
  540. $record->sent_date = REQUEST_TIME;
  541. // Used to separate different portions of the body string.
  542. $divider = str_repeat('-', 60) . "\n";
  543. // Load the attachments.
  544. $attachments = $mailer->GetAttachments();
  545. $record->body = '';
  546. // If there's more than one item to display, add a divider.
  547. if (!empty($mailer->AltBody) || !empty($attachments)) {
  548. $record->body .= t('Body') . ":\n";
  549. $record->body .= $divider;
  550. }
  551. // Add the body field.
  552. if (isset($mailer->Body)) {
  553. $record->body .= $mailer->Body;
  554. }
  555. else {
  556. $record->body .= t('*No message body*') . ":\n";
  557. }
  558. // The AltBody value is optional.
  559. if (!empty($mailer->AltBody)) {
  560. $record->body .= "\n";
  561. $record->body .= $divider;
  562. $record->body .= t('Alternative body') . ":\n";
  563. $record->body .= $divider;
  564. $record->body .= $mailer->AltBody;
  565. }
  566. // List the attachments.
  567. if (!empty($attachments)) {
  568. $record->body .= "\n";
  569. $record->body .= $divider;
  570. $record->body .= t('Attachments') . ":\n";
  571. $record->body .= $divider;
  572. foreach ($attachments as $file) {
  573. $record->body .= t('Filename') . ':' . $file[1] . "\n";
  574. $record->body .= t('Name') . ':' . $file[2] . "\n";
  575. $record->body .= t('Encoding') . ':' . $file[3] . "\n";
  576. $record->body .= t('Type') . ':' . $file[4] . "\n";
  577. $record->body .= "\n";
  578. }
  579. }
  580. drupal_write_record('maillog', $record);
  581. }
  582. // Display the e-mail using Devel module.
  583. if (variable_get('maillog_devel', TRUE) && function_exists('dpm')) {
  584. $devel_msg = array();
  585. $devel_msg[t('Subject')] = $mailer->Subject;
  586. $devel_msg[t('From')] = $from;
  587. $devel_msg[t('To')] = $to;
  588. $devel_msg[t('Reply-To')] = isset($headers['Reply-To']) ? $headers['Reply-To'] : NULL;
  589. $devel_msg[t('Headers')] = $headers;
  590. $devel_msg[t('Body')] = $mailer->Body;
  591. $devel_msg[t('Alternative body')] = $mailer->AltBody;
  592. $devel_msg[t('Attachments')] = $mailer->GetAttachments();
  593. dpm($devel_msg, 'maillog');
  594. }
  595. }
  596. $error = FALSE;
  597. // Email delivery was disabled.
  598. if (!variable_get('smtp_deliver', TRUE)) {
  599. if ($logging) {
  600. $params = array(
  601. '@from' => $from,
  602. '@to' => $to,
  603. );
  604. watchdog('smtp', 'Email delivery is disabled, did not send email from @from to @to.', $params);
  605. }
  606. }
  607. else {
  608. if (!$mailer->send()) {
  609. $params = array(
  610. '@from' => $from,
  611. '@to' => $to,
  612. '!error_message' => $mailer->ErrorInfo
  613. );
  614. if (variable_get('smtp_queue_fail', FALSE)) {
  615. if ($logging) {
  616. watchdog('smtp', 'Error sending e-mail from @from to @to, will retry on cron run : !error_message.', $params, WATCHDOG_ERROR);
  617. }
  618. smtp_failed_messages($message);
  619. }
  620. elseif ($logging) {
  621. $error = TRUE;
  622. watchdog('smtp', 'Error sending e-mail from @from to @to : !error_message', $params, WATCHDOG_ERROR);
  623. }
  624. }
  625. elseif (variable_get('smtp_debugging', SMTP_LOGGING_ERRORS) == SMTP_LOGGING_ALL) {
  626. watchdog('smtp', 'Sent mail to: @to', array('@to' => $to));
  627. }
  628. }
  629. $mailer->SmtpClose();
  630. return !$error;
  631. }
  632. /**
  633. * Splits the input into parts based on the given boundary.
  634. *
  635. * Swiped from Mail::MimeDecode, with modifications based on Drupal's coding
  636. * standards and this bug report: http://pear.php.net/bugs/bug.php?id=6495
  637. *
  638. * @param input
  639. * A string containing the body text to parse.
  640. * @param boundary
  641. * A string with the boundary string to parse on.
  642. * @return
  643. * An array containing the resulting mime parts
  644. */
  645. protected function _boundary_split($input, $boundary) {
  646. $parts = array();
  647. $bs_possible = drupal_substr($boundary, 2, -2);
  648. $bs_check = '\"' . $bs_possible . '\"';
  649. if ($boundary == $bs_check) {
  650. $boundary = $bs_possible;
  651. }
  652. $tmp = explode('--' . $boundary, $input);
  653. for ($i = 1; $i < count($tmp); $i++) {
  654. if (trim($tmp[$i])) {
  655. $parts[] = $tmp[$i];
  656. }
  657. }
  658. return $parts;
  659. } // End of _smtp_boundary_split().
  660. /**
  661. * Strips the headers from the body part.
  662. *
  663. * @param input
  664. * A string containing the body part to strip.
  665. * @return
  666. * A string with the stripped body part.
  667. */
  668. protected function _remove_headers($input) {
  669. $part_array = explode("\n", $input);
  670. // will strip these headers according to RFC2045
  671. $headers_to_strip = array( 'Content-Type', 'Content-Transfer-Encoding', 'Content-ID', 'Content-Disposition');
  672. $pattern = '/^(' . implode('|', $headers_to_strip) . '):/';
  673. while (count($part_array) > 0) {
  674. // ignore trailing spaces/newlines
  675. $line = rtrim($part_array[0]);
  676. // if the line starts with a known header string
  677. if (preg_match($pattern, $line)) {
  678. $line = rtrim(array_shift($part_array));
  679. // remove line containing matched header.
  680. // if line ends in a ';' and the next line starts with four spaces, it's a continuation
  681. // of the header split onto the next line. Continue removing lines while we have this condition.
  682. while (substr($line, -1) == ';' && count($part_array) > 0 && substr($part_array[0], 0, 4) == ' ') {
  683. $line = rtrim(array_shift($part_array));
  684. }
  685. }
  686. else {
  687. // no match header, must be past headers; stop searching.
  688. break;
  689. }
  690. }
  691. $output = implode("\n", $part_array);
  692. return $output;
  693. } // End of _smtp_remove_headers().
  694. /**
  695. * Returns a string that is contained within another string.
  696. *
  697. * Returns the string from within $source that is some where after $target
  698. * and is between $beginning_character and $ending_character.
  699. *
  700. * @param $source
  701. * A string containing the text to look through.
  702. * @param $target
  703. * A string containing the text in $source to start looking from.
  704. * @param $beginning_character
  705. * A string containing the character just before the sought after text.
  706. * @param $ending_character
  707. * A string containing the character just after the sought after text.
  708. * @return
  709. * A string with the text found between the $beginning_character and the
  710. * $ending_character.
  711. */
  712. protected function _get_substring($source, $target, $beginning_character, $ending_character) {
  713. $search_start = strpos($source, $target) + 1;
  714. $first_character = strpos($source, $beginning_character, $search_start) + 1;
  715. $second_character = strpos($source, $ending_character, $first_character) + 1;
  716. $substring = drupal_substr($source, $first_character, $second_character - $first_character);
  717. $string_length = drupal_strlen($substring) - 1;
  718. if ($substring[$string_length] == $ending_character) {
  719. $substring = drupal_substr($substring, 0, $string_length);
  720. }
  721. return trim($substring);
  722. } // End of _smtp_get_substring().
  723. /**
  724. * Returns an array of name and email address from a string.
  725. *
  726. * @param $input
  727. * A string that contains different possible combinations of names and
  728. * email address.
  729. * @return
  730. * An array containing a name and an email address.
  731. */
  732. protected function _get_components($input) {
  733. $input = trim($input);
  734. $components = array(
  735. 'input' => $input,
  736. 'name' => '',
  737. 'email' => '',
  738. );
  739. // If the input is a valid email address in its entirety, then there is
  740. // nothing to do, just return that.
  741. if (valid_email_address($input)) {
  742. $components['email'] = $input;
  743. return $components;
  744. }
  745. // Check if $input has one of the following formats, extract what we can:
  746. // some name <address@example.com>
  747. // "another name" <address@example.com>
  748. // <address@example.com>
  749. if (preg_match('/^"?([^"\t\n]*)"?\s*<([^>\t\n]*)>$/', $input, $matches)) {
  750. $components['name'] = trim($matches[1]);
  751. $components['email'] = trim($matches[2]);
  752. }
  753. return $components;
  754. }
  755. }