mailjet.mail.inc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. <?php
  2. /**
  3. * @file
  4. * Mail processing.
  5. */
  6. class MailjetSmtpMailSystem implements MailSystemInterface {
  7. protected $AllowHtml;
  8. /**
  9. * Concatenate and wrap the e-mail body for either
  10. * plain-text or HTML emails.
  11. *
  12. * @param array $message
  13. * A message array, as described in hook_mail_alter().
  14. *
  15. * @return string
  16. * The formatted $message.
  17. */
  18. public function format(array $message) {
  19. $this->AllowHtml = variable_get('mailjet_allowhtml', TRUE);
  20. // Join the body array into one string.
  21. $message['body'] = implode("\n\n", $message['body']);
  22. if (! $this->AllowHtml) {
  23. // Convert any HTML to plain-text.
  24. $message['body'] = drupal_html_to_text($message['body']);
  25. // Wrap the mail body for sending.
  26. $message['body'] = drupal_wrap_mail($message['body']);
  27. }
  28. return $message;
  29. }
  30. /**
  31. * Send the e-mail message.
  32. *
  33. * @see drupal_mail()
  34. *
  35. * @param array $message
  36. * A message array, as described in hook_mail_alter().
  37. *
  38. * @return bool
  39. * TRUE if the mail was successfully accepted, otherwise FALSE.
  40. */
  41. public function mail(array $message) {
  42. $id = $message['id'];
  43. $to = $message['to'];
  44. $from = $message['from'];
  45. $subject = $message['subject'];
  46. $body = $message['body'];
  47. $headers = $message['headers'];
  48. if (isset($message['params']['subject'])) {
  49. $subject = $message['params']['subject'];
  50. }
  51. if (isset($message['params']['body'][0])) {
  52. $body = $message['params']['body'][0];
  53. }
  54. $path = libraries_get_path('phpmailer');
  55. if (file_exists($path . '/class.phpmailer.php')) {
  56. require_once $path . '/class.phpmailer.php';
  57. }
  58. else {
  59. drupal_set_message(t('Unable to send mail : PHPMailer library does not exist.'), 'error');
  60. return FALSE;
  61. }
  62. // Create a new PHPMailer object - autoloaded from registry.
  63. $mailer = new PHPMailer();
  64. $from_name = variable_get('site_name', '');
  65. // Hack to fix reply-to issue.
  66. $properfrom = variable_get('site_mail', '');
  67. if (!empty($properfrom)) {
  68. $headers['From'] = $properfrom;
  69. }
  70. if (!isset($headers['Reply-To']) || empty($headers['Reply-To'])) {
  71. if (strpos($from, '<')) {
  72. $reply = preg_replace('/>.*/', '', preg_replace('/.*</', '', $from));
  73. }
  74. else {
  75. $reply = $from;
  76. }
  77. $headers['Reply-To'] = $reply;
  78. }
  79. // Blank value will let the e-mail address appear.
  80. if ($from == NULL || $from == '') {
  81. // If from e-mail address is blank, use smtp_from config option.
  82. if (($from = variable_get('mailjet_from', '')) == '') {
  83. // If smtp_from config option is blank, use site_email.
  84. if (($from = variable_get('site_email', '')) == '') {
  85. drupal_set_message(t('There is no submitted from address.'), 'error');
  86. if (variable_get('mailjet_debug')) {
  87. watchdog('mailjet', 'There is no submitted from address.', array(), WATCHDOG_ERROR);
  88. }
  89. return FALSE;
  90. }
  91. }
  92. }
  93. if (preg_match('/^"?.*"?\s*<.*>$/', $from)) {
  94. // . == Matches any single character except line break characters \r and
  95. // \n.
  96. // * == Repeats the previous item zero or more times.
  97. $from_name = preg_replace('/"?([^("\t\n)]*)"?.*$/', '$1', $from);
  98. $from = preg_replace("/(.*)\<(.*)\>/i", '$2', $from);
  99. }
  100. elseif (!valid_email_address($from)) {
  101. drupal_set_message(t('The submitted from address (@from) is not valid.', array('@from' => $from)), 'error');
  102. if (variable_get('mailjet_debug')) {
  103. watchdog('mailjet', 'The submitted from address (@from) is not valid.', array('@from' => $from), WATCHDOG_ERROR);
  104. }
  105. return FALSE;
  106. }
  107. // Defines the From value $from_nameto what we expect.
  108. $mailer->SetFrom($from, $from_name);
  109. // Create the list of 'To:' recipients.
  110. $torecipients = explode(',', $to);
  111. foreach ($torecipients as $torecipient) {
  112. if (strpos($torecipient, '<') !== FALSE) {
  113. $toparts = explode(' <', $torecipient);
  114. $toname = $toparts[0];
  115. $toaddr = rtrim($toparts[1], '>');
  116. }
  117. else {
  118. $toname = '';
  119. $toaddr = $torecipient;
  120. }
  121. $mailer->AddAddress($toaddr, $toname);
  122. }
  123. // Parse the headers of the message and set the PHPMailer object's settings
  124. // accordingly.
  125. foreach ($headers as $key => $value) {
  126. switch (drupal_strtolower($key)) {
  127. case 'from':
  128. if ($from == NULL or $from == '') {
  129. // If a from value was already given, then set based on header.
  130. // Should be the most common situation since drupal_mail moves the
  131. // from to headers.
  132. $from = $value;
  133. $mailer->From = $value;
  134. // Then from can be out of sync with from_name !
  135. $mailer->FromName = '';
  136. $mailer->Sender = $value;
  137. }
  138. break;
  139. case 'content-type':
  140. // Parse several values on the Content-type header, storing them in
  141. // an array like key=value -> $vars['key']='value'
  142. $vars = explode('; ', $value);
  143. foreach ($vars as $i => $var) {
  144. if ($cut = strpos($var, '=')) {
  145. $new_var = drupal_strtolower(drupal_substr($var, $cut + 1));
  146. $new_key = drupal_substr($var, 0, $cut);
  147. unset($vars[$i]);
  148. $vars[$new_key] = $new_var;
  149. }
  150. }
  151. // Set the charset based on the provided value, if there is one.
  152. $mailer->CharSet = isset($vars['charset']) ? $vars['charset'] : 'utf-8';
  153. switch ($vars[0]) {
  154. case 'text/plain':
  155. // The message includes only a plain text part.
  156. $mailer->IsHTML(FALSE);
  157. $content_type = 'text/plain';
  158. break;
  159. case 'text/html':
  160. // The message includes only an HTML part.
  161. $mailer->IsHTML(TRUE);
  162. $content_type = 'text/html';
  163. break;
  164. case 'multipart/related':
  165. // Get the boundary ID from the Content-Type header.
  166. $boundary = $this->getSubstrings($value, 'boundary', '"', '"');
  167. // The message includes an HTML part w/inline attachments.
  168. $mailer->ContentType = $content_type = 'multipart/related; boundary="' . $boundary . '"';
  169. break;
  170. case 'multipart/alternative':
  171. // The message includes both a plain text and an HTML part.
  172. $mailer->ContentType = $content_type = 'multipart/alternative';
  173. // Get the boundary ID from the Content-Type header.
  174. $boundary = $this->getSubstrings($value, 'boundary', '"', '"');
  175. break;
  176. case 'multipart/mixed':
  177. // The message includes one or more attachments.
  178. $mailer->ContentType = $content_type = 'multipart/mixed';
  179. // Get the boundary ID from the Content-Type header.
  180. $boundary = $this->getSubstrings($value, 'boundary', '"', '"');
  181. break;
  182. default:
  183. // Everything else is unsuppored by PHPMailer.
  184. drupal_set_message(t('The Content-Type of your message is not supported by PHPMailer and will be sent as text/plain instead.'), 'error');
  185. if (variable_get('mailjet_debug')) {
  186. watchdog('mailjet', 'The Content-Type of your message is not supported by PHPMailer and will be sent as text/plain instead.', array(), WATCHDOG_ERROR);
  187. }
  188. // Force the Content-Type to be text/plain.
  189. $mailer->IsHTML(FALSE);
  190. $content_type = 'text/plain';
  191. }
  192. break;
  193. case 'reply-to':
  194. // Only add a "reply-to" if it's not the same as "return-path".
  195. if ($value != $headers['Return-Path']) {
  196. if (strpos($value, '<') !== FALSE) {
  197. $reply_to_parts = explode('<', $value);
  198. $reply_to_name = trim($reply_to_parts[0]);
  199. $reply_to_name = trim($reply_to_name, '"');
  200. $reply_to_addr = rtrim($reply_to_parts[1], '>');
  201. $mailer->AddReplyTo($reply_to_addr, $reply_to_name);
  202. }
  203. else {
  204. $mailer->AddReplyTo($value);
  205. }
  206. }
  207. break;
  208. case 'content-transfer-encoding':
  209. $mailer->Encoding = $value;
  210. break;
  211. case 'return-path':
  212. case 'mime-version':
  213. case 'x-mailer':
  214. break;
  215. case 'errors-to':
  216. $mailer->AddCustomHeader('Errors-To: ' . $value);
  217. break;
  218. case 'cc':
  219. $ccrecipients = explode(',', $value);
  220. foreach ($ccrecipients as $ccrecipient) {
  221. if (strpos($ccrecipient, '<') !== FALSE) {
  222. $ccparts = explode(' <', $ccrecipient);
  223. $ccname = $ccparts[0];
  224. $ccaddr = rtrim($ccparts[1], '>');
  225. }
  226. else {
  227. $ccname = '';
  228. $ccaddr = $ccrecipient;
  229. }
  230. $mailer->AddBCC($ccaddr, $ccname);
  231. }
  232. break;
  233. case 'bcc':
  234. $bccrecipients = explode(',', $value);
  235. foreach ($bccrecipients as $bccrecipient) {
  236. if (strpos($bccrecipient, '<') !== FALSE) {
  237. $bccparts = explode(' <', $bccrecipient);
  238. $bccname = $bccparts[0];
  239. $bccaddr = rtrim($bccparts[1], '>');
  240. }
  241. else {
  242. $bccname = '';
  243. $bccaddr = $bccrecipient;
  244. }
  245. $mailer->AddBCC($bccaddr, $bccname);
  246. }
  247. break;
  248. default:
  249. // The header key is not special - add it as is.
  250. $mailer->AddCustomHeader($key . ': ' . $value);
  251. }
  252. }
  253. $mailer->AddCustomHeader('X-Mailer:Mailjet-for-Drupal/1.0');
  254. $mailer->Subject = $subject;
  255. // Processes the message's body.
  256. switch ($content_type) {
  257. case 'multipart/related':
  258. $mailer->Body = $body;
  259. break;
  260. case 'multipart/alternative':
  261. // Split the body based on the boundary ID.
  262. $body_parts = $this->boundarySplit($body, $boundary);
  263. foreach ($body_parts as $body_part) {
  264. // If plain/text within the body part, add it to $mailer->AltBody.
  265. if (strpos($body_part, 'text/plain')) {
  266. // Clean up the text.
  267. $body_part = trim($this->removeHeaders(trim($body_part)));
  268. // Include it as part of the mail object.
  269. $mailer->AltBody = $body_part;
  270. }
  271. // If plain/html within the body part, add it to $mailer->Body.
  272. elseif (strpos($body_part, 'text/html')) {
  273. // Clean up the text.
  274. $body_part = trim($this->removeHeaders(trim($body_part)));
  275. // Include it as part of the mail object.
  276. $mailer->Body = $body_part;
  277. }
  278. }
  279. break;
  280. case 'multipart/mixed':
  281. // Split the body based on the boundary ID.
  282. $body_parts = $this->boundarySplit($body, $boundary);
  283. // Determine if there is an HTML part for when adding the plain
  284. // text part.
  285. $text_plain = FALSE;
  286. $text_html = FALSE;
  287. foreach ($body_parts as $body_part) {
  288. if (strpos($body_part, 'text/plain')) {
  289. $text_plain = TRUE;
  290. }
  291. if (strpos($body_part, 'text/html')) {
  292. $text_html = TRUE;
  293. }
  294. }
  295. foreach ($body_parts as $body_part) {
  296. // If test/plain within the body part, add it to either
  297. // $mailer->AltBody or $mailer->Body, depending on whether there is
  298. // also a text/html part ot not.
  299. if (strpos($body_part, 'multipart/alternative')) {
  300. // Clean up the text.
  301. $body_part = trim($this->removeHeaders(trim($body_part)));
  302. // Get boundary ID from the Content-Type header.
  303. $boundary2 = $this->getSubstrings($body_part, 'boundary', '"', '"');
  304. // Split the body based on the boundary ID.
  305. $body_parts2 = $this->boundarySplit($body_part, $boundary2);
  306. foreach ($body_parts2 as $body_part2) {
  307. // If plain/text within the body part, add it to $mailer->AltBody.
  308. if (strpos($body_part2, 'text/plain')) {
  309. // Clean up the text.
  310. $body_part2 = trim($this->removeHeaders(trim($body_part2)));
  311. // Include it as part of the mail object.
  312. $mailer->AltBody = $body_part2;
  313. $mailer->ContentType = 'multipart/mixed';
  314. }
  315. // If plain/html within the body part, add it to $mailer->Body.
  316. elseif (strpos($body_part2, 'text/html')) {
  317. // Clean up the text.
  318. $body_part2 = trim($this->removeHeaders(trim($body_part2)));
  319. // Include it as part of the mail object.
  320. $mailer->Body = $body_part2;
  321. $mailer->ContentType = 'multipart/mixed';
  322. }
  323. }
  324. }
  325. // If text/plain within the body part, add it to $mailer->Body.
  326. elseif (strpos($body_part, 'text/plain')) {
  327. // Clean up the text.
  328. $body_part = trim($this->removeHeaders(trim($body_part)));
  329. if ($text_html) {
  330. $mailer->AltBody = $body_part;
  331. $mailer->IsHTML(TRUE);
  332. $mailer->ContentType = 'multipart/mixed';
  333. }
  334. else {
  335. $mailer->Body = $body_part;
  336. $mailer->IsHTML(FALSE);
  337. $mailer->ContentType = 'multipart/mixed';
  338. }
  339. }
  340. // If text/html within the body part, add it to $mailer->Body.
  341. elseif (strpos($body_part, 'text/html')) {
  342. // Clean up the text.
  343. $body_part = trim($this->removeHeaders(trim($body_part)));
  344. // Include it as part of the mail object.
  345. $mailer->Body = $body_part;
  346. $mailer->IsHTML(TRUE);
  347. $mailer->ContentType = 'multipart/mixed';
  348. }
  349. // Add the attachment.
  350. elseif (strpos($body_part, 'Content-Disposition: attachment;')) {
  351. $file_path = $this->getSubstrings($body_part, 'filename=', '"', '"');
  352. $file_name = $this->getSubstrings($body_part, ' name=', '"', '"');
  353. $file_encoding = $this->getSubstrings($body_part, 'Content-Transfer-Encoding', ' ', "\n");
  354. $file_type = $this->getSubstrings($body_part, 'Content-Type', ' ', ';');
  355. if (file_exists($file_path)) {
  356. if (!$mailer->AddAttachment($file_path, $file_name, $file_encoding, $filetype)) {
  357. drupal_set_message(t('Attahment could not be found or accessed.'));
  358. }
  359. }
  360. else {
  361. // Clean up the text.
  362. $body_part = trim($this->removeHeaders(trim($body_part)));
  363. if (drupal_strtolower($file_encoding) == 'base64') {
  364. $attachment = base64_decode($body_part);
  365. }
  366. elseif (drupal_strtolower($file_encoding) == 'quoted-printable') {
  367. $attachment = quoted_printable_decode($body_part);
  368. }
  369. else {
  370. $attachment = $body_part;
  371. }
  372. $attachment_new_filename = tempnam(realpath(file_directory_temp()), 'smtp');
  373. $file_path = file_save_data($attachment, $attachment_new_filename, FILE_EXISTS_RENAME);
  374. if (!$mailer->AddAttachment($file_path, $file_name)) {
  375. drupal_set_message(t('Attachment could not be found or accessed.'));
  376. }
  377. }
  378. }
  379. }
  380. break;
  381. default:
  382. $mailer->Body = $body;
  383. break;
  384. }
  385. // Set the authentication settings.
  386. $username = variable_get('mailjet_username', '');
  387. $password = variable_get('mailjet_password', '');
  388. // If username and password are given, use SMTP authentication.
  389. if ($username != '' && $password != '') {
  390. $mailer->SMTPAuth = TRUE;
  391. $mailer->Username = $username;
  392. $mailer->Password = $password;
  393. }
  394. // Set the protocol prefix for the smtp host.
  395. switch (variable_get('mailjet_protocol', 'standard')) {
  396. case 'ssl':
  397. $mailer->SMTPSecure = 'ssl';
  398. break;
  399. case 'tls':
  400. $mailer->SMTPSecure = 'tls';
  401. break;
  402. default:
  403. $mailer->SMTPSecure = '';
  404. }
  405. // Set other connection settings.
  406. $mailer->Host = variable_get('mailjet_host', 'in.mailjet.com');
  407. $mailer->Port = variable_get('mailjet_port', '25');
  408. $mailer->Mailer = 'smtp';
  409. if (variable_get('mailjet_debug')) {
  410. watchdog('mailjet', 'Sending mail to: @to', array('@to' => $to));
  411. }
  412. // Try to send e-mail. If it fails, set watchdog entry.
  413. if (!$mailer->Send()) {
  414. if (variable_get('mailjet_debug')) {
  415. watchdog('mailjet',
  416. 'Error sending e-mail from @from to @to : @error_message',
  417. array(
  418. '@from' => $from,
  419. '@to' => $to,
  420. '@error_message' => $mailer->ErrorInfo),
  421. WATCHDOG_ERROR);
  422. }
  423. return FALSE;
  424. }
  425. $mailer->SmtpClose();
  426. return TRUE;
  427. }
  428. /**
  429. * Splits the input into parts based on the given boundary.
  430. *
  431. * Swiped from Mail::MimeDecode, with modifications based on Drupal's coding
  432. * standards and this bug report: http://pear.php.net/bugs/bug.php?id=6495
  433. *
  434. * @param string $input
  435. * A string containing the body text to parse.
  436. * @param string $boundary
  437. * A string with the boundary string to parse on.
  438. *
  439. * @return array
  440. * An array containing the resulting mime parts
  441. */
  442. protected function boundarySplit($input, $boundary) {
  443. $parts = array();
  444. $bs_possible = drupal_substr($boundary, 2, -2);
  445. $bs_check = '\"' . $bs_possible . '\"';
  446. if ($boundary == $bs_check) {
  447. $boundary = $bs_possible;
  448. }
  449. $tmp = explode('--' . $boundary, $input);
  450. for ($i = 1; $i < count($tmp); $i++) {
  451. if (trim($tmp[$i])) {
  452. $parts[] = $tmp[$i];
  453. }
  454. }
  455. return $parts;
  456. }
  457. /**
  458. * Strips the headers from the body part.
  459. *
  460. * @param string $input
  461. * A string containing the body part to strip.
  462. *
  463. * @return string
  464. * A string with the stripped body part.
  465. */
  466. protected function removeHeaders($input) {
  467. $part_array = explode("\n", $input);
  468. if (strpos($part_array[0], 'Content') !== FALSE) {
  469. if (strpos($part_array[1], 'Content') !== FALSE) {
  470. if (strpos($part_array[2], 'Content') !== FALSE) {
  471. array_shift($part_array);
  472. array_shift($part_array);
  473. array_shift($part_array);
  474. }
  475. else {
  476. array_shift($part_array);
  477. array_shift($part_array);
  478. }
  479. }
  480. else {
  481. array_shift($part_array);
  482. }
  483. }
  484. $output = implode("\n", $part_array);
  485. return $output;
  486. }
  487. /**
  488. * Returns a string that is contained within another string.
  489. *
  490. * Returns the string from within $source that is some where after $target
  491. * and is between $beginning_character and $ending_character.
  492. *
  493. * @param string $source
  494. * A string containing the text to look through.
  495. * @param string $target
  496. * A string containing the text in $source to start looking from.
  497. * @param string $beginning_character
  498. * A string containing the character just before the sought after text.
  499. * @param string $ending_character
  500. * A string containing the character just after the sought after text.
  501. *
  502. * @return string
  503. * A string with the text found between the $beginning_character and the
  504. * $ending_character.
  505. */
  506. protected function getSubstrings($source, $target, $beginning_character, $ending_character) {
  507. $search_start = strpos($source, $target) + 1;
  508. $first_character = strpos($source, $beginning_character, $search_start) + 1;
  509. $second_character = strpos($source, $ending_character, $first_character) + 1;
  510. $substring = drupal_substr($source, $first_character, $second_character - $first_character);
  511. $string_length = drupal_strlen($substring) - 1;
  512. if ($substring[$string_length] == $ending_character) {
  513. $substring = drupal_substr($substring, 0, $string_length);
  514. }
  515. return $substring;
  516. }
  517. }