dolibarr  17.0.4
CMailFile.class.php
Go to the documentation of this file.
1 <?php
33 use OAuth\Common\Storage\DoliStorage;
34 use OAuth\Common\Consumer\Credentials;
35 
41 class CMailFile
42 {
43  public $sendcontext;
44  public $sendmode;
45  public $sendsetup;
46 
50  public $subject;
51  public $addr_from; // From: Label and EMail of sender (must include '<>'). For example '<myemail@example.com>' or 'John Doe <myemail@example.com>' or '<myemail+trackingid@example.com>'). Note that with gmail smtps, value here is forced by google to account (but not the reply-to).
52  // Sender: Who send the email ("Sender" has sent emails on behalf of "From").
53  // Use it when the "From" is an email of a domain that is a SPF protected domain, and sending smtp server is not this domain. In such case, add Sender field with an email of the protected domain.
54  // Return-Path: Email where to send bounds.
55  public $reply_to; // Reply-To: Email where to send replies from mailer software (mailer use From if reply-to not defined, Gmail use gmail account if reply-to not defined)
56  public $errors_to; // Errors-To: Email where to send errors.
57  public $addr_to;
58  public $addr_cc;
59  public $addr_bcc;
60  public $trackid;
61 
62  public $mixed_boundary;
63  public $related_boundary;
64  public $alternative_boundary;
65  public $deliveryreceipt;
66 
67  public $atleastonefile;
68 
69  public $msg;
70  public $eol;
71  public $eol2;
72 
76  public $error = '';
77 
81  public $errors = array();
82 
83  public $smtps; // Contains SMTPs object (if this method is used)
84  public $phpmailer; // Contains PHPMailer object (if this method is used)
85 
89  public $transport;
90 
94  public $mailer;
95 
99  public $logger;
100 
104  public $css;
106  public $styleCSS;
108  public $bodyCSS;
109 
110  public $msgid;
111  public $headers;
112  public $message;
116  public $filename_list = array();
120  public $mimetype_list = array();
124  public $mimefilename_list = array();
128  public $cid_list = array();
129 
130  // Image
131  public $html;
132  public $image_boundary;
133  public $atleastoneimage = 0; // at least one image file with file=xxx.ext into content (TODO Debug this. How can this case be tested. Remove if not used).
134  public $html_images = array();
135  public $images_encoded = array();
136  public $image_types = array(
137  'gif' => 'image/gif',
138  'jpg' => 'image/jpeg',
139  'jpeg' => 'image/jpeg',
140  'jpe' => 'image/jpeg',
141  'bmp' => 'image/bmp',
142  'png' => 'image/png',
143  'tif' => 'image/tiff',
144  'tiff' => 'image/tiff',
145  );
146 
147 
170  public function __construct($subject, $to, $from, $msg, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $addr_cc = "", $addr_bcc = "", $deliveryreceipt = 0, $msgishtml = 0, $errors_to = '', $css = '', $trackid = '', $moreinheader = '', $sendcontext = 'standard', $replyto = '', $upload_dir_tmp = '')
171  {
172  global $conf, $dolibarr_main_data_root, $user;
173 
174  dol_syslog("CMailFile::CMailfile: charset=".$conf->file->character_set_client." from=$from, to=$to, addr_cc=$addr_cc, addr_bcc=$addr_bcc, errors_to=$errors_to, replyto=$replyto trackid=$trackid sendcontext=$sendcontext", LOG_DEBUG);
175  dol_syslog("CMailFile::CMailfile: subject=".$subject.", deliveryreceipt=".$deliveryreceipt.", msgishtml=".$msgishtml, LOG_DEBUG);
176 
177 
178  // Clean values of $mimefilename_list
179  if (is_array($mimefilename_list)) {
180  foreach ($mimefilename_list as $key => $val) {
181  $mimefilename_list[$key] = dol_string_unaccent($mimefilename_list[$key]);
182  }
183  }
184 
185  $cid_list = array();
186 
187  $this->sendcontext = $sendcontext;
188 
189  // Define this->sendmode ('mail', 'smtps', 'siwftmailer', ...) according to $sendcontext ('standard', 'emailing', 'ticket')
190  $this->sendmode = '';
191  if (!empty($this->sendcontext)) {
192  $smtpContextKey = strtoupper($this->sendcontext);
193  $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_'.$smtpContextKey);
194  if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
195  $this->sendmode = $smtpContextSendMode;
196  }
197  }
198  if (empty($this->sendmode)) {
199  $this->sendmode = (!empty($conf->global->MAIN_MAIL_SENDMODE) ? $conf->global->MAIN_MAIL_SENDMODE : 'mail');
200  }
201 
202  // We define end of line (RFC 821).
203  $this->eol = "\r\n";
204  // We define end of line for header fields (RFC 822bis section 2.3 says header must contains \r\n).
205  $this->eol2 = "\r\n";
206  if (!empty($conf->global->MAIN_FIX_FOR_BUGGED_MTA)) {
207  $this->eol = "\n";
208  $this->eol2 = "\n";
209  $moreinheader = str_replace("\r\n", "\n", $moreinheader);
210  }
211 
212  // On defini mixed_boundary
213  $this->mixed_boundary = "multipart_x.".time().".x_boundary";
214 
215  // On defini related_boundary
216  $this->related_boundary = 'mul_'.dol_hash(uniqid("dolibarr2"), 3); // Force md5 hash (does not contains special chars)
217 
218  // On defini alternative_boundary
219  $this->alternative_boundary = 'mul_'.dol_hash(uniqid("dolibarr3"), 3); // Force md5 hash (does not contains special chars)
220 
221  if (empty($subject)) {
222  dol_syslog("CMailFile::CMailfile: Try to send an email with empty subject");
223  $this->error = 'ErrorSubjectIsRequired';
224  return;
225  }
226  if (empty($msg)) {
227  dol_syslog("CMailFile::CMailfile: Try to send an email with empty body");
228  $msg = '.'; // Avoid empty message (with empty message content, you will see a multipart structure)
229  }
230 
231  // Detect if message is HTML (use fast method)
232  if ($msgishtml == -1) {
233  $this->msgishtml = 0;
234  if (dol_textishtml($msg)) {
235  $this->msgishtml = 1;
236  }
237  } else {
238  $this->msgishtml = $msgishtml;
239  }
240 
241  global $dolibarr_main_url_root;
242 
243  // Define $urlwithroot
244  $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
245  $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
246  //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
247 
248  // Replace relative /viewimage to absolute path
249  $msg = preg_replace('/src="'.preg_quote(DOL_URL_ROOT, '/').'\/viewimage\.php/ims', 'src="'.$urlwithroot.'/viewimage.php', $msg, -1);
250 
251  if (!empty($conf->global->MAIN_MAIL_FORCE_CONTENT_TYPE_TO_HTML)) {
252  $this->msgishtml = 1; // To force to send everything with content type html.
253  }
254 
255  // Detect images
256  if ($this->msgishtml) {
257  $this->html = $msg;
258 
259  $findimg = 0;
260  if (!empty($conf->global->MAIN_MAIL_ADD_INLINE_IMAGES_IF_IN_MEDIAS)) { // Off by default
261  // Search into the body for <img tags of links in medias files to replace them with an embedded file
262  // Note because media links are public, this should be useless, except avoid blocking images with email browser.
263  // This convert an embedd file with src="/viewimage.php?modulepart... into a cid link
264  // TODO Exclude viewimage used for the read tracker ?
265  $findimg = $this->findHtmlImages($dolibarr_main_data_root.'/medias');
266  if ($findimg<0) {
267  dol_syslog("CMailFile::CMailfile: Error on findHtmlImages");
268  $this->error = 'ErrorInAddAttachementsImageBaseOnMedia';
269  return;
270  }
271  }
272 
273  if (!empty($conf->global->MAIN_MAIL_ADD_INLINE_IMAGES_IF_DATA)) {
274  // Search into the body for <img src="data:image/ext;base64,..." to replace them with an embedded file
275  // This convert an embedded file with src="data:image... into a cid link + attached file
276  $resultImageData = $this->findHtmlImagesIsSrcData($upload_dir_tmp);
277  if ($resultImageData<0) {
278  dol_syslog("CMailFile::CMailfile: Error on findHtmlImagesInSrcData");
279  $this->error = 'ErrorInAddAttachementsImageBaseOnMedia';
280  return;
281  }
282  $findimg += $resultImageData;
283  }
284 
285  // Set atleastoneimage if there is at least one embedded file (into ->html_images)
286  if ($findimg > 0) {
287  foreach ($this->html_images as $i => $val) {
288  if ($this->html_images[$i]) {
289  $this->atleastoneimage = 1;
290  if ($this->html_images[$i]['type'] == 'cidfromdata') {
291  if (!in_array($this->html_images[$i]['fullpath'], $filename_list)) {
292  // If this file path is not already into the $filename_list, we add it.
293  $posindice = count($filename_list);
294  $filename_list[$posindice] = $this->html_images[$i]['fullpath'];
295  $mimetype_list[$posindice] = $this->html_images[$i]['content_type'];
296  $mimefilename_list[$posindice] = $this->html_images[$i]['name'];
297  } else {
298  $posindice = array_search($this->html_images[$i]['fullpath'], $filename_list);
299  }
300  // We complete the array of cid_list
301  $cid_list[$posindice] = $this->html_images[$i]['cid'];
302  }
303  dol_syslog("CMailFile::CMailfile: html_images[$i]['name']=".$this->html_images[$i]['name'], LOG_DEBUG);
304  }
305  }
306  }
307  }
308  //var_dump($filename_list);
309  //var_dump($cid_list);exit;
310 
311  // Set atleastoneimage if there is at least one file (into $filename_list array)
312  if (is_array($filename_list)) {
313  foreach ($filename_list as $i => $val) {
314  if ($filename_list[$i]) {
315  $this->atleastonefile = 1;
316  dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i]." cid_list[$i]=".$cid_list[$i], LOG_DEBUG);
317  }
318  }
319  }
320 
321  // Add auto copy to if not already in $to (Note: Adding bcc for specific modules are also done from pages)
322  // For example MAIN_MAIL_AUTOCOPY_TO can be 'email@example.com, __USER_EMAIL__, ...'
323  if (!empty($conf->global->MAIN_MAIL_AUTOCOPY_TO)) {
324  $listofemailstoadd = explode(',', $conf->global->MAIN_MAIL_AUTOCOPY_TO);
325  foreach ($listofemailstoadd as $key => $val) {
326  $emailtoadd = $listofemailstoadd[$key];
327  if (trim($emailtoadd) == '__USER_EMAIL__') {
328  if (!empty($user) && !empty($user->email)) {
329  $emailtoadd = $user->email;
330  } else {
331  $emailtoadd = '';
332  }
333  }
334  if ($emailtoadd && preg_match('/'.preg_quote($emailtoadd, '/').'/i', $to)) {
335  $emailtoadd = ''; // Email already in the "To"
336  }
337  if ($emailtoadd) {
338  $listofemailstoadd[$key] = $emailtoadd;
339  } else {
340  unset($listofemailstoadd[$key]);
341  }
342  }
343  if (!empty($listofemailstoadd)) {
344  $addr_bcc .= ($addr_bcc ? ', ' : '').join(', ', $listofemailstoadd);
345  }
346  }
347 
348  $this->subject = $subject;
349  $this->addr_to = dol_sanitizeEmail($to);
350  $this->addr_from = dol_sanitizeEmail($from);
351  $this->msg = $msg;
352  $this->addr_cc = dol_sanitizeEmail($addr_cc);
353  $this->addr_bcc = dol_sanitizeEmail($addr_bcc);
354  $this->deliveryreceipt = $deliveryreceipt;
355  if (empty($replyto)) {
356  $replyto = dol_sanitizeEmail($from);
357  }
358  $this->reply_to = dol_sanitizeEmail($replyto);
359  $this->errors_to = dol_sanitizeEmail($errors_to);
360  $this->trackid = $trackid;
361  // Set arrays with attached files info
362  $this->filename_list = $filename_list;
363  $this->mimetype_list = $mimetype_list;
364  $this->mimefilename_list = $mimefilename_list;
365  $this->cid_list = $cid_list;
366 
367  if (!empty($conf->global->MAIN_MAIL_FORCE_SENDTO)) {
368  $this->addr_to = dol_sanitizeEmail($conf->global->MAIN_MAIL_FORCE_SENDTO);
369  $this->addr_cc = '';
370  $this->addr_bcc = '';
371  }
372 
373  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
374  if (!empty($this->sendcontext)) {
375  $smtpContextKey = strtoupper($this->sendcontext);
376  $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_'.$smtpContextKey);
377  if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
378  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_'.$smtpContextKey;
379  }
380  }
381 
382  dol_syslog("CMailFile::CMailfile: sendmode=".$this->sendmode." addr_bcc=$addr_bcc, replyto=$replyto", LOG_DEBUG);
383 
384  // We set all data according to choosed sending method.
385  // We also set a value for ->msgid
386  if ($this->sendmode == 'mail') {
387  // Use mail php function (default PHP method)
388  // ------------------------------------------
389 
390  $smtp_headers = "";
391  $mime_headers = "";
392  $text_body = "";
393  $files_encoded = "";
394 
395  // Define smtp_headers (this also set ->msgid)
396  $smtp_headers = $this->write_smtpheaders();
397  if (!empty($moreinheader)) {
398  $smtp_headers .= $moreinheader; // $moreinheader contains the \r\n
399  }
400 
401  // Define mime_headers
402  $mime_headers = $this->write_mimeheaders($filename_list, $mimefilename_list);
403 
404  if (!empty($this->html)) {
405  if (!empty($css)) {
406  $this->css = $css;
407  $this->buildCSS(); // Build a css style (mode = all) into this->styleCSS and this->bodyCSS
408  }
409 
410  $msg = $this->html;
411  }
412 
413  // Define body in text_body
414  $text_body = $this->write_body($msg);
415 
416  // Add attachments to text_encoded
417  if (!empty($this->atleastonefile)) {
418  $files_encoded = $this->write_files($filename_list, $mimetype_list, $mimefilename_list, $cid_list);
419  }
420 
421  // We now define $this->headers and $this->message
422  $this->headers = $smtp_headers.$mime_headers;
423  // On nettoie le header pour qu'il ne se termine pas par un retour chariot.
424  // This avoid also empty lines at end that can be interpreted as mail injection by email servers.
425  $this->headers = preg_replace("/([\r\n]+)$/i", "", $this->headers);
426 
427  //$this->message = $this->eol.'This is a message with multiple parts in MIME format.'.$this->eol;
428  $this->message = 'This is a message with multiple parts in MIME format.'.$this->eol;
429  $this->message .= $text_body.$files_encoded;
430  $this->message .= "--".$this->mixed_boundary."--".$this->eol;
431  } elseif ($this->sendmode == 'smtps') {
432  // Use SMTPS library
433  // ------------------------------------------
434 
435  require_once DOL_DOCUMENT_ROOT.'/core/class/smtps.class.php';
436  $smtps = new SMTPs();
437  $smtps->setCharSet($conf->file->character_set_client);
438 
439  // Encode subject if required.
440  $subjecttouse = $this->subject;
441  if (!ascii_check($subjecttouse)) {
442  $subjecttouse = $this->encodetorfc2822($subjecttouse);
443  }
444 
445  $smtps->setSubject($subjecttouse);
446  $smtps->setTO($this->getValidAddress($this->addr_to, 0, 1));
447  $smtps->setFrom($this->getValidAddress($this->addr_from, 0, 1));
448  $smtps->setTrackId($this->trackid);
449  $smtps->setReplyTo($this->getValidAddress($this->reply_to, 0, 1));
450 
451  if (!empty($moreinheader)) {
452  $smtps->setMoreInHeader($moreinheader);
453  }
454 
455  if (!empty($this->html)) {
456  if (!empty($css)) {
457  $this->css = $css;
458  $this->buildCSS();
459  }
460  $msg = $this->html;
461  $msg = $this->checkIfHTML($msg);
462  }
463 
464  // Replace . alone on a new line with .. to avoid to have SMTP interpret this as end of message
465  $msg = preg_replace('/(\r|\n)\.(\r|\n)/ims', '\1..\2', $msg);
466 
467  if ($this->msgishtml) {
468  $smtps->setBodyContent($msg, 'html');
469  } else {
470  $smtps->setBodyContent($msg, 'plain');
471  }
472 
473  if ($this->atleastoneimage) {
474  foreach ($this->images_encoded as $img) {
475  $smtps->setImageInline($img['image_encoded'], $img['name'], $img['content_type'], $img['cid']);
476  }
477  }
478 
479  if (!empty($this->atleastonefile)) {
480  foreach ($filename_list as $i => $val) {
481  $content = file_get_contents($filename_list[$i]);
482  $smtps->setAttachment($content, $mimefilename_list[$i], $mimetype_list[$i], $cid_list[$i]);
483  }
484  }
485 
486  $smtps->setCC($this->addr_cc);
487  $smtps->setBCC($this->addr_bcc);
488  $smtps->setErrorsTo($this->errors_to);
489  $smtps->setDeliveryReceipt($this->deliveryreceipt);
490  if (!empty($conf->global->$keyforsslseflsigned)) {
491  $smtps->setOptions(array('ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true)));
492  }
493 
494  $host = dol_getprefix('email');
495  $this->msgid = time().'.SMTPs-dolibarr-'.$this->trackid.'@'.$host;
496 
497  $this->smtps = $smtps;
498  } elseif ($this->sendmode == 'swiftmailer') {
499  // Use Swift Mailer library
500  $host = dol_getprefix('email');
501 
502  require_once DOL_DOCUMENT_ROOT.'/includes/swiftmailer/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php';
503 
504  // egulias autoloader lib
505  require_once DOL_DOCUMENT_ROOT.'/includes/swiftmailer/autoload.php';
506 
507  require_once DOL_DOCUMENT_ROOT.'/includes/swiftmailer/lib/swift_required.php';
508 
509  // Create the message
510  //$this->message = Swift_Message::newInstance();
511  $this->message = new Swift_Message();
512  //$this->message = new Swift_SignedMessage();
513  // Adding a trackid header to a message
514  $headers = $this->message->getHeaders();
515  $headers->addTextHeader('X-Dolibarr-TRACKID', $this->trackid.'@'.$host);
516  $this->msgid = time().'.swiftmailer-dolibarr-'.$this->trackid.'@'.$host;
517  $headerID = $this->msgid;
518  $msgid = $headers->get('Message-ID');
519  $msgid->setId($headerID);
520  $headers->addIdHeader('References', $headerID);
521  // TODO if (!empty($moreinheader)) ...
522 
523  // Give the message a subject
524  try {
525  $result = $this->message->setSubject($this->subject);
526  } catch (Exception $e) {
527  $this->errors[] = $e->getMessage();
528  }
529 
530  // Set the From address with an associative array
531  //$this->message->setFrom(array('john@doe.com' => 'John Doe'));
532  if (!empty($this->addr_from)) {
533  try {
534  if (!empty($conf->global->MAIN_FORCE_DISABLE_MAIL_SPOOFING)) {
535  // Prevent email spoofing for smtp server with a strict configuration
536  $regexp = '/([a-z0-9_\.\-\+])+\@(([a-z0-9\-])+\.)+([a-z0-9]{2,4})+/i'; // This regular expression extracts all emails from a string
537  $adressEmailFrom = array();
538  $emailMatchs = preg_match_all($regexp, $from, $adressEmailFrom);
539  $adressEmailFrom = reset($adressEmailFrom);
540  if ($emailMatchs !== false && filter_var($conf->global->MAIN_MAIL_SMTPS_ID, FILTER_VALIDATE_EMAIL) && $conf->global->MAIN_MAIL_SMTPS_ID !== $adressEmailFrom) {
541  $this->message->setFrom($conf->global->MAIN_MAIL_SMTPS_ID);
542  } else {
543  $this->message->setFrom($this->getArrayAddress($this->addr_from));
544  }
545  } else {
546  $this->message->setFrom($this->getArrayAddress($this->addr_from));
547  }
548  } catch (Exception $e) {
549  $this->errors[] = $e->getMessage();
550  }
551  }
552 
553  // Set the To addresses with an associative array
554  if (!empty($this->addr_to)) {
555  try {
556  $this->message->setTo($this->getArrayAddress($this->addr_to));
557  } catch (Exception $e) {
558  $this->errors[] = $e->getMessage();
559  }
560  }
561 
562  if (!empty($this->reply_to)) {
563  try {
564  $this->message->SetReplyTo($this->getArrayAddress($this->reply_to));
565  } catch (Exception $e) {
566  $this->errors[] = $e->getMessage();
567  }
568  }
569 
570  try {
571  $this->message->setCharSet($conf->file->character_set_client);
572  } catch (Exception $e) {
573  $this->errors[] = $e->getMessage();
574  }
575 
576  if (!empty($this->html)) {
577  if (!empty($css)) {
578  $this->css = $css;
579  $this->buildCSS();
580  }
581  $msg = $this->html;
582  $msg = $this->checkIfHTML($msg);
583  }
584 
585  if ($this->atleastoneimage) {
586  foreach ($this->images_encoded as $img) {
587  //$img['fullpath'],$img['image_encoded'],$img['name'],$img['content_type'],$img['cid']
588  $attachment = Swift_Image::fromPath($img['fullpath']);
589  // embed image
590  $imgcid = $this->message->embed($attachment);
591  // replace cid by the one created by swiftmail in html message
592  $msg = str_replace("cid:".$img['cid'], $imgcid, $msg);
593  }
594  }
595 
596  if ($this->msgishtml) {
597  $this->message->setBody($msg, 'text/html');
598  // And optionally an alternative body
599  $this->message->addPart(html_entity_decode(strip_tags($msg)), 'text/plain');
600  } else {
601  $this->message->setBody($msg, 'text/plain');
602  // And optionally an alternative body
603  $this->message->addPart(dol_nl2br($msg), 'text/html');
604  }
605 
606  if (!empty($this->atleastonefile)) {
607  foreach ($filename_list as $i => $val) {
608  //$this->message->attach(Swift_Attachment::fromPath($filename_list[$i],$mimetype_list[$i]));
609  $attachment = Swift_Attachment::fromPath($filename_list[$i], $mimetype_list[$i]);
610  if (!empty($mimefilename_list[$i])) {
611  $attachment->setFilename($mimefilename_list[$i]);
612  }
613  $this->message->attach($attachment);
614  }
615  }
616 
617  if (!empty($this->addr_cc)) {
618  try {
619  $this->message->setCc($this->getArrayAddress($this->addr_cc));
620  } catch (Exception $e) {
621  $this->errors[] = $e->getMessage();
622  }
623  }
624  if (!empty($this->addr_bcc)) {
625  try {
626  $this->message->setBcc($this->getArrayAddress($this->addr_bcc));
627  } catch (Exception $e) {
628  $this->errors[] = $e->getMessage();
629  }
630  }
631  //if (!empty($this->errors_to)) $this->message->setErrorsTo($this->getArrayAddress($this->errors_to));
632  if (isset($this->deliveryreceipt) && $this->deliveryreceipt == 1) {
633  try {
634  $this->message->setReadReceiptTo($this->getArrayAddress($this->addr_from));
635  } catch (Exception $e) {
636  $this->errors[] = $e->getMessage();
637  }
638  }
639  } else {
640  // Send mail method not correctly defined
641  // --------------------------------------
642  $this->error = 'Bad value for sendmode';
643  }
644  }
645 
646 
652  public function sendfile()
653  {
654  global $conf, $db, $langs;
655 
656  $errorlevel = error_reporting();
657  //error_reporting($errorlevel ^ E_WARNING); // Desactive warnings
658 
659  $res = false;
660 
661  if (empty($conf->global->MAIN_DISABLE_ALL_MAILS)) {
662  require_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
663  $hookmanager = new HookManager($db);
664  $hookmanager->initHooks(array('mail'));
665 
666  $parameters = array();
667  $action = '';
668  $reshook = $hookmanager->executeHooks('sendMail', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
669  if ($reshook < 0) {
670  $this->error = "Error in hook maildao sendMail ".$reshook;
671  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
672 
673  return $reshook;
674  }
675  if ($reshook == 1) { // Hook replace standard code
676  return true;
677  }
678 
679  $sendingmode = $this->sendmode;
680  if ($this->sendcontext == 'emailing' && !empty($conf->global->MAILING_NO_USING_PHPMAIL) && $sendingmode == 'mail') {
681  // List of sending methods
682  $listofmethods = array();
683  $listofmethods['mail'] = 'PHP mail function';
684  //$listofmethods['simplemail']='Simplemail class';
685  $listofmethods['smtps'] = 'SMTP/SMTPS socket library';
686 
687  // EMailing feature may be a spam problem, so when you host several users/instance, having this option may force each user to use their own SMTP agent.
688  // You ensure that every user is using its own SMTP server when using the mass emailing module.
689  $linktoadminemailbefore = '<a href="'.DOL_URL_ROOT.'/admin/mails.php">';
690  $linktoadminemailend = '</a>';
691  $this->error = $langs->trans("MailSendSetupIs", $listofmethods[$sendingmode]);
692  $this->errors[] = $langs->trans("MailSendSetupIs", $listofmethods[$sendingmode]);
693  $this->error .= '<br>'.$langs->trans("MailSendSetupIs2", $linktoadminemailbefore, $linktoadminemailend, $langs->transnoentitiesnoconv("MAIN_MAIL_SENDMODE"), $listofmethods['smtps']);
694  $this->errors[] = $langs->trans("MailSendSetupIs2", $linktoadminemailbefore, $linktoadminemailend, $langs->transnoentitiesnoconv("MAIN_MAIL_SENDMODE"), $listofmethods['smtps']);
695  if (!empty($conf->global->MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS)) {
696  $this->error .= '<br>'.$langs->trans("MailSendSetupIs3", $conf->global->MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS);
697  $this->errors[] = $langs->trans("MailSendSetupIs3", $conf->global->MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS);
698  }
699  return false;
700  }
701 
702  // Check number of recipient is lower or equal than MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL
703  if (empty($conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL)) {
704  $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL = 10;
705  }
706  $tmparray1 = explode(',', $this->addr_to);
707  if (count($tmparray1) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL) {
708  $this->error = 'Too much recipients in to:';
709  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_WARNING);
710  return false;
711  }
712  if (empty($conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL)) {
713  $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL = 10;
714  }
715  $tmparray2 = explode(',', $this->addr_cc);
716  if (count($tmparray2) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL) {
717  $this->error = 'Too much recipients in cc:';
718  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_WARNING);
719  return false;
720  }
721  if (empty($conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL)) {
722  $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL = 10;
723  }
724  $tmparray3 = explode(',', $this->addr_bcc);
725  if (count($tmparray3) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL) {
726  $this->error = 'Too much recipients in bcc:';
727  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_WARNING);
728  return false;
729  }
730  if (empty($conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL)) {
731  $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL = 10;
732  }
733  if ((count($tmparray1) + count($tmparray2) + count($tmparray3)) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL) {
734  $this->error = 'Too much recipients in to:, cc:, bcc:';
735  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_WARNING);
736  return false;
737  }
738 
739  $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER';
740  $keyforsmtpport = 'MAIN_MAIL_SMTP_PORT';
741  $keyforsmtpid = 'MAIN_MAIL_SMTPS_ID';
742  $keyforsmtppw = 'MAIN_MAIL_SMTPS_PW';
743  $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE';
744  $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE';
745  $keyfortls = 'MAIN_MAIL_EMAIL_TLS';
746  $keyforstarttls = 'MAIN_MAIL_EMAIL_STARTTLS';
747  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
748  if (!empty($this->sendcontext)) {
749  $smtpContextKey = strtoupper($this->sendcontext);
750  $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_'.$smtpContextKey);
751  if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
752  $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER_'.$smtpContextKey;
753  $keyforsmtpport = 'MAIN_MAIL_SMTP_PORT_'.$smtpContextKey;
754  $keyforsmtpid = 'MAIN_MAIL_SMTPS_ID_'.$smtpContextKey;
755  $keyforsmtppw = 'MAIN_MAIL_SMTPS_PW_'.$smtpContextKey;
756  $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE_'.$smtpContextKey;
757  $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE_'.$smtpContextKey;
758  $keyfortls = 'MAIN_MAIL_EMAIL_TLS_'.$smtpContextKey;
759  $keyforstarttls = 'MAIN_MAIL_EMAIL_STARTTLS_'.$smtpContextKey;
760  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_'.$smtpContextKey;
761  }
762  }
763 
764  // Action according to choosed sending method
765  if ($this->sendmode == 'mail') {
766  // Use mail php function (default PHP method)
767  // ------------------------------------------
768  dol_syslog("CMailFile::sendfile addr_to=".$this->addr_to.", subject=".$this->subject, LOG_DEBUG);
769  dol_syslog("CMailFile::sendfile header=\n".$this->headers, LOG_DEBUG);
770  //dol_syslog("CMailFile::sendfile message=\n".$message);
771 
772  // If Windows, sendmail_from must be defined
773  if (isset($_SERVER["WINDIR"])) {
774  if (empty($this->addr_from)) {
775  $this->addr_from = 'robot@example.com';
776  }
777  @ini_set('sendmail_from', $this->getValidAddress($this->addr_from, 2));
778  }
779 
780  // Force parameters
781  //dol_syslog("CMailFile::sendfile conf->global->".$keyforsmtpserver."=".$conf->global->$keyforsmtpserver." cpnf->global->".$keyforsmtpport."=".$conf->global->$keyforsmtpport, LOG_DEBUG);
782  if (!empty($conf->global->$keyforsmtpserver)) {
783  ini_set('SMTP', $conf->global->$keyforsmtpserver);
784  }
785  if (!empty($conf->global->$keyforsmtpport)) {
786  ini_set('smtp_port', $conf->global->$keyforsmtpport);
787  }
788 
789  $res = true;
790  if ($res && !$this->subject) {
791  $this->error = "Failed to send mail with php mail to HOST=".ini_get('SMTP').", PORT=".ini_get('smtp_port')."<br>Subject is empty";
792  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
793  $res = false;
794  }
795  $dest = $this->getValidAddress($this->addr_to, 2);
796  if ($res && !$dest) {
797  $this->error = "Failed to send mail with php mail to HOST=".ini_get('SMTP').", PORT=".ini_get('smtp_port')."<br>Recipient address '$dest' invalid";
798  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
799  $res = false;
800  }
801 
802  if ($res) {
803  $additionnalparam = ''; // By default
804  if (!empty($conf->global->MAIN_MAIL_ALLOW_SENDMAIL_F)) {
805  // le "Return-Path" (retour des messages bounced) dans les header ne fonctionne pas avec tous les MTA
806  // Le forcage de la valeur grace à l'option -f de sendmail est donc possible si la constante MAIN_MAIL_ALLOW_SENDMAIL_F est definie.
807  // Having this variable defined may create problems with some sendmail (option -f refused)
808  // Having this variable not defined may create problems with some other sendmail (option -f required)
809  $additionnalparam .= ($additionnalparam ? ' ' : '').(!empty($conf->global->MAIN_MAIL_ERRORS_TO) ? '-f'.$this->getValidAddress($conf->global->MAIN_MAIL_ERRORS_TO, 2) : ($this->addr_from != '' ? '-f'.$this->getValidAddress($this->addr_from, 2) : ''));
810  }
811  if (!empty($conf->global->MAIN_MAIL_SENDMAIL_FORCE_BA)) { // To force usage of -ba option. This option tells sendmail to read From: or Sender: to setup sender
812  $additionnalparam .= ($additionnalparam ? ' ' : '').'-ba';
813  }
814 
815  if (!empty($conf->global->MAIN_MAIL_SENDMAIL_FORCE_ADDPARAM)) {
816  $additionnalparam .= ($additionnalparam ? ' ' : '').'-U '.$additionnalparam; // Use -U to add additionnal params
817  }
818 
819  $linuxlike = 1;
820  if (preg_match('/^win/i', PHP_OS)) {
821  $linuxlike = 0;
822  }
823  if (preg_match('/^mac/i', PHP_OS)) {
824  $linuxlike = 0;
825  }
826 
827  dol_syslog("CMailFile::sendfile: mail start".($linuxlike ? '' : " HOST=".ini_get('SMTP').", PORT=".ini_get('smtp_port')).", additionnal_parameters=".$additionnalparam, LOG_DEBUG);
828 
829  $this->message = stripslashes($this->message);
830 
831  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
832  $this->dump_mail();
833  }
834 
835  // Encode subject if required.
836  $subjecttouse = $this->subject;
837  if (!ascii_check($subjecttouse)) {
838  $subjecttouse = $this->encodetorfc2822($subjecttouse);
839  }
840 
841  if (!empty($additionnalparam)) {
842  $res = mail($dest, $subjecttouse, $this->message, $this->headers, $additionnalparam);
843  } else {
844  $res = mail($dest, $subjecttouse, $this->message, $this->headers);
845  }
846 
847  if (!$res) {
848  $langs->load("errors");
849  $this->error = "Failed to send mail with php mail";
850  if (!$linuxlike) {
851  $this->error .= " to HOST=".ini_get('SMTP').", PORT=".ini_get('smtp_port'); // This values are value used only for non linuxlike systems
852  }
853  $this->error .= ".<br>";
854  $this->error .= $langs->trans("ErrorPhpMailDelivery");
855  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
856 
857  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
858  $this->save_dump_mail_in_err();
859  }
860  } else {
861  dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
862  }
863  }
864 
865  if (isset($_SERVER["WINDIR"])) {
866  @ini_restore('sendmail_from');
867  }
868 
869  // Restore parameters
870  if (!empty($conf->global->$keyforsmtpserver)) {
871  ini_restore('SMTP');
872  }
873  if (!empty($conf->global->$keyforsmtpport)) {
874  ini_restore('smtp_port');
875  }
876  } elseif ($this->sendmode == 'smtps') {
877  if (!is_object($this->smtps)) {
878  $this->error = "Failed to send mail with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport."<br>Constructor of object CMailFile was not initialized without errors.";
879  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
880  return false;
881  }
882 
883  // Use SMTPS library
884  // ------------------------------------------
885  $this->smtps->setTransportType(0); // Only this method is coded in SMTPs library
886 
887  // Clean parameters
888  if (empty($conf->global->$keyforsmtpserver)) {
889  $conf->global->$keyforsmtpserver = ini_get('SMTP');
890  }
891  if (empty($conf->global->$keyforsmtpport)) {
892  $conf->global->$keyforsmtpport = ini_get('smtp_port');
893  }
894 
895  // If we use SSL/TLS
896  $server = $conf->global->$keyforsmtpserver;
897  $secure = '';
898  if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
899  $secure = 'ssl';
900  }
901  if (!empty($conf->global->$keyforstarttls) && function_exists('openssl_open')) {
902  $secure = 'tls';
903  }
904  $server = ($secure ? $secure.'://' : '').$server;
905 
906  $port = $conf->global->$keyforsmtpport;
907 
908  $this->smtps->setHost($server);
909  $this->smtps->setPort($port); // 25, 465...;
910 
911  $loginid = '';
912  $loginpass = '';
913  if (!empty($conf->global->$keyforsmtpid)) {
914  $loginid = $conf->global->$keyforsmtpid;
915  $this->smtps->setID($loginid);
916  }
917  if (!empty($conf->global->$keyforsmtppw)) {
918  $loginpass = $conf->global->$keyforsmtppw;
919  $this->smtps->setPW($loginpass);
920  }
921 
922  if (getDolGlobalString($keyforsmtpauthtype) === "XOAUTH2") {
923  require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // define $supportedoauth2array
924 
925  $keyforsupportedoauth2array = $conf->global->$keyforsmtpoauthservice;
926  if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
927  $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
928  } else {
929  $keyforprovider = '';
930  }
931  $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array);
932  $keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME';
933 
934  if (isset($supportedoauth2array)) {
935  $OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : ''));
936  } else {
937  $OAUTH_SERVICENAME = 'Unknown';
938  }
939 
940  require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
941 
942  $storage = new DoliStorage($db, $conf, $keyforprovider);
943  try {
944  $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
945  $expire = false;
946  // Is token expired or will token expire in the next 30 seconds
947  if (is_object($tokenobj)) {
948  $expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30));
949  }
950  // Token expired so we refresh it
951  if (is_object($tokenobj) && $expire) {
952  $credentials = new Credentials(
953  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_ID'),
954  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_SECRET'),
955  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_URLAUTHORIZE')
956  );
957  $serviceFactory = new \OAuth\ServiceFactory();
958  $oauthname = explode('-', $OAUTH_SERVICENAME);
959  // ex service is Google-Emails we need only the first part Google
960  $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array());
961  // We have to save the token because Google give it only once
962  $refreshtoken = $tokenobj->getRefreshToken();
963  $tokenobj = $apiService->refreshAccessToken($tokenobj);
964  $tokenobj->setRefreshToken($refreshtoken);
965  $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
966  }
967 
968  $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
969  if (is_object($tokenobj)) {
970  $this->smtps->setToken($tokenobj->getAccessToken());
971  } else {
972  $this->error = "Token not found";
973  }
974  } catch (Exception $e) {
975  // Return an error if token not found
976  $this->error = $e->getMessage();
977  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
978  }
979  }
980 
981  $res = true;
982  $from = $this->smtps->getFrom('org');
983  if ($res && !$from) {
984  $this->error = "Failed to send mail with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport." - Sender address '$from' invalid";
985  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
986  $res = false;
987  }
988  $dest = $this->smtps->getTo();
989  if ($res && !$dest) {
990  $this->error = "Failed to send mail with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport." - Recipient address '$dest' invalid";
991  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
992  $res = false;
993  }
994 
995  if ($res) {
996  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
997  $this->smtps->setDebug(true);
998  }
999 
1000  $result = $this->smtps->sendMsg();
1001 
1002  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1003  $this->dump_mail();
1004  }
1005 
1006  if (! $result) {
1007  $smtperrorcode = $this->smtps->lastretval; // SMTP error code
1008  dol_syslog("CMailFile::sendfile: mail SMTP error code ".$smtperrorcode, LOG_WARNING);
1009 
1010  if ($smtperrorcode == '421') { // Try later
1011  // TODO Add a delay and try again
1012  /*
1013  dol_syslog("CMailFile::sendfile: Try later error, so we wait and we retry");
1014  sleep(2);
1015 
1016  $result = $this->smtps->sendMsg();
1017 
1018  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1019  $this->dump_mail();
1020  }
1021  */
1022  }
1023  }
1024 
1025  $result = $this->smtps->getErrors(); // applicative error code (not SMTP error code)
1026  if (empty($this->error) && empty($result)) {
1027  dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
1028  $res = true;
1029  } else {
1030  if (empty($this->error)) {
1031  $this->error = $result;
1032  }
1033  dol_syslog("CMailFile::sendfile: mail end error with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport." - ".$this->error, LOG_ERR);
1034  $res = false;
1035 
1036  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1037  $this->save_dump_mail_in_err();
1038  }
1039  }
1040  }
1041  } elseif ($this->sendmode == 'swiftmailer') {
1042  // Use Swift Mailer library
1043  // ------------------------------------------
1044  require_once DOL_DOCUMENT_ROOT.'/includes/swiftmailer/lib/swift_required.php';
1045 
1046  // Clean parameters
1047  if (empty($conf->global->$keyforsmtpserver)) {
1048  $conf->global->$keyforsmtpserver = ini_get('SMTP');
1049  }
1050  if (empty($conf->global->$keyforsmtpport)) {
1051  $conf->global->$keyforsmtpport = ini_get('smtp_port');
1052  }
1053 
1054  // If we use SSL/TLS
1055  $server = $conf->global->$keyforsmtpserver;
1056  $secure = '';
1057  if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
1058  $secure = 'ssl';
1059  }
1060  if (!empty($conf->global->$keyforstarttls) && function_exists('openssl_open')) {
1061  $secure = 'tls';
1062  }
1063 
1064  $this->transport = new Swift_SmtpTransport($server, $conf->global->$keyforsmtpport, $secure);
1065 
1066  if (!empty($conf->global->$keyforsmtpid)) {
1067  $this->transport->setUsername($conf->global->$keyforsmtpid);
1068  }
1069  if (!empty($conf->global->$keyforsmtppw) && getDolGlobalString($keyforsmtpauthtype) != "XOAUTH2") {
1070  $this->transport->setPassword($conf->global->$keyforsmtppw);
1071  }
1072  if (getDolGlobalString($keyforsmtpauthtype) === "XOAUTH2") {
1073  require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // define $supportedoauth2array
1074 
1075  $keyforsupportedoauth2array = getDolGlobalString($keyforsmtpoauthservice);
1076  if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
1077  $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
1078  } else {
1079  $keyforprovider = '';
1080  }
1081  $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array);
1082  $keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME';
1083 
1084  $OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : ''));
1085 
1086  require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
1087 
1088  $storage = new DoliStorage($db, $conf, $keyforprovider);
1089 
1090  try {
1091  $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
1092  $expire = false;
1093  // Is token expired or will token expire in the next 30 seconds
1094  if (is_object($tokenobj)) {
1095  $expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30));
1096  }
1097  // Token expired so we refresh it
1098  if (is_object($tokenobj) && $expire) {
1099  $credentials = new Credentials(
1100  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_ID'),
1101  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_SECRET'),
1102  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_URLAUTHORIZE')
1103  );
1104  $serviceFactory = new \OAuth\ServiceFactory();
1105  $oauthname = explode('-', $OAUTH_SERVICENAME);
1106  // ex service is Google-Emails we need only the first part Google
1107  $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array());
1108  // We have to save the token because Google give it only once
1109  $refreshtoken = $tokenobj->getRefreshToken();
1110  $tokenobj = $apiService->refreshAccessToken($tokenobj);
1111  $tokenobj->setRefreshToken($refreshtoken);
1112  $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
1113  }
1114  if (is_object($tokenobj)) {
1115  $this->transport->setAuthMode('XOAUTH2');
1116  $this->transport->setPassword($tokenobj->getAccessToken());
1117  } else {
1118  $this->errors[] = "Token not found";
1119  }
1120  } catch (Exception $e) {
1121  // Return an error if token not found
1122  $this->errors[] = $e->getMessage();
1123  dol_syslog("CMailFile::sendfile: mail end error=".$e->getMessage(), LOG_ERR);
1124  }
1125  }
1126  if (!empty($conf->global->$keyforsslseflsigned)) {
1127  $this->transport->setStreamOptions(array('ssl' => array('allow_self_signed' => true, 'verify_peer' => false)));
1128  }
1129  //$smtps->_msgReplyTo = 'reply@web.com';
1130 
1131  // Switch content encoding to base64 - avoid the doubledot issue with quoted-printable
1132  $contentEncoderBase64 = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
1133  $this->message->setEncoder($contentEncoderBase64);
1134 
1135  // Create the Mailer using your created Transport
1136  $this->mailer = new Swift_Mailer($this->transport);
1137 
1138  // DKIM SIGN
1139  if ($conf->global->MAIN_MAIL_EMAIL_DKIM_ENABLED) {
1140  $privateKey = $conf->global->MAIN_MAIL_EMAIL_DKIM_PRIVATE_KEY;
1141  $domainName = $conf->global->MAIN_MAIL_EMAIL_DKIM_DOMAIN;
1142  $selector = $conf->global->MAIN_MAIL_EMAIL_DKIM_SELECTOR;
1143  $signer = new Swift_Signers_DKIMSigner($privateKey, $domainName, $selector);
1144  $this->message->attachSigner($signer->ignoreHeader('Return-Path'));
1145  }
1146 
1147  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1148  // To use the ArrayLogger
1149  $this->logger = new Swift_Plugins_Loggers_ArrayLogger();
1150  // Or to use the Echo Logger
1151  //$this->logger = new Swift_Plugins_Loggers_EchoLogger();
1152  $this->mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($this->logger));
1153  }
1154  // send mail
1155  $failedRecipients = array();
1156  try {
1157  $result = $this->mailer->send($this->message, $failedRecipients);
1158  } catch (Exception $e) {
1159  $this->errors[] = $e->getMessage();
1160  }
1161  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1162  $this->dump_mail();
1163  }
1164 
1165  $res = true;
1166  if (!empty($this->error) || !empty($this->errors) || !$result) {
1167  if (!empty($failedRecipients)) {
1168  $this->errors[] = 'Transport failed for the following addresses: "' . join('", "', $failedRecipients) . '".';
1169  }
1170  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
1171  $res = false;
1172 
1173  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1174  $this->save_dump_mail_in_err();
1175  }
1176  } else {
1177  dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
1178  }
1179  } else {
1180  // Send mail method not correctly defined
1181  // --------------------------------------
1182 
1183  return 'Bad value for sendmode';
1184  }
1185 
1186  // Now we delete image files that were created dynamically to manage data inline files
1187  foreach ($this->html_images as $val) {
1188  if (!empty($val['type']) && $val['type'] == 'cidfromdata') {
1189  //dol_delete($val['fullpath']);
1190  }
1191  }
1192 
1193  $parameters = array('sent' => $res);
1194  $action = '';
1195  $reshook = $hookmanager->executeHooks('sendMailAfter', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1196  if ($reshook < 0) {
1197  $this->error = "Error in hook maildao sendMailAfter ".$reshook;
1198  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
1199 
1200  return $reshook;
1201  }
1202  } else {
1203  $this->error = 'No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS';
1204  dol_syslog("CMailFile::sendfile: ".$this->error, LOG_WARNING);
1205  }
1206 
1207  error_reporting($errorlevel); // Reactive niveau erreur origine
1208  return $res;
1209  }
1210 
1217  public static function encodetorfc2822($stringtoencode)
1218  {
1219  global $conf;
1220  return '=?'.$conf->file->character_set_client.'?B?'.base64_encode($stringtoencode).'?=';
1221  }
1222 
1223  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1230  private function _encode_file($sourcefile)
1231  {
1232  // phpcs:enable
1233  $newsourcefile = dol_osencode($sourcefile);
1234 
1235  if (is_readable($newsourcefile)) {
1236  $contents = file_get_contents($newsourcefile); // Need PHP 4.3
1237  $encoded = chunk_split(base64_encode($contents), 76, $this->eol); // 76 max is defined into http://tools.ietf.org/html/rfc2047
1238  return $encoded;
1239  } else {
1240  $this->error = "Error in _encode_file() method: Can't read file '".$sourcefile."'";
1241  dol_syslog("CMailFile::_encode_file: ".$this->error, LOG_ERR);
1242  return -1;
1243  }
1244  }
1245 
1246 
1247  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1255  public function dump_mail()
1256  {
1257  // phpcs:enable
1258  global $conf, $dolibarr_main_data_root;
1259 
1260  if (@is_writeable($dolibarr_main_data_root)) { // Avoid fatal error on fopen with open_basedir
1261  $outputfile = $dolibarr_main_data_root."/dolibarr_mail.log";
1262  $fp = fopen($outputfile, "w"); // overwrite
1263 
1264  if ($this->sendmode == 'mail') {
1265  fputs($fp, $this->headers);
1266  fputs($fp, $this->eol); // This eol is added by the mail function, so we add it in log
1267  fputs($fp, $this->message);
1268  } elseif ($this->sendmode == 'smtps') {
1269  fputs($fp, $this->smtps->log); // this->smtps->log is filled only if MAIN_MAIL_DEBUG was set to on
1270  } elseif ($this->sendmode == 'swiftmailer') {
1271  fputs($fp, $this->logger->dump()); // this->logger is filled only if MAIN_MAIL_DEBUG was set to on
1272  }
1273 
1274  fclose($fp);
1275  if (!empty($conf->global->MAIN_UMASK)) {
1276  @chmod($outputfile, octdec($conf->global->MAIN_UMASK));
1277  }
1278  }
1279  }
1280 
1281  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1288  public function save_dump_mail_in_err()
1289  {
1290  global $dolibarr_main_data_root;
1291 
1292  if (@is_writeable($dolibarr_main_data_root)) { // Avoid fatal error on fopen with open_basedir
1293  $srcfile = $dolibarr_main_data_root."/dolibarr_mail.log";
1294  if (getDolGlobalString('MAIN_MAIL_DEBUG_ERR_WITH_DATE')) {
1295  $destfile = $dolibarr_main_data_root."/dolibarr_mail.".dol_print_date(dol_now(), 'dayhourlog', 'gmt').".err";
1296  } else {
1297  $destfile = $dolibarr_main_data_root."/dolibarr_mail.err";
1298  }
1299 
1300  dol_move($srcfile, $destfile, 0, 1, 0, 0);
1301  }
1302  }
1303 
1304 
1311  public function checkIfHTML($msg)
1312  {
1313  if (!preg_match('/^[\s\t]*<html/i', $msg)) {
1314  $out = "<html><head><title></title>";
1315  if (!empty($this->styleCSS)) {
1316  $out .= $this->styleCSS;
1317  }
1318  $out .= "</head><body";
1319  if (!empty($this->bodyCSS)) {
1320  $out .= $this->bodyCSS;
1321  }
1322  $out .= ">";
1323  $out .= $msg;
1324  $out .= "</body></html>";
1325  } else {
1326  $out = $msg;
1327  }
1328 
1329  return $out;
1330  }
1331 
1337  public function buildCSS()
1338  {
1339  if (!empty($this->css)) {
1340  // Style CSS
1341  $this->styleCSS = '<style type="text/css">';
1342  $this->styleCSS .= 'body {';
1343 
1344  if ($this->css['bgcolor']) {
1345  $this->styleCSS .= ' background-color: '.$this->css['bgcolor'].';';
1346  $this->bodyCSS .= ' bgcolor="'.$this->css['bgcolor'].'"';
1347  }
1348  if ($this->css['bgimage']) {
1349  // TODO recuperer cid
1350  $this->styleCSS .= ' background-image: url("cid:'.$this->css['bgimage_cid'].'");';
1351  }
1352  $this->styleCSS .= '}';
1353  $this->styleCSS .= '</style>';
1354  }
1355  }
1356 
1357 
1358  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1364  public function write_smtpheaders()
1365  {
1366  // phpcs:enable
1367  global $conf;
1368  $out = "";
1369 
1370  $host = dol_getprefix('email');
1371 
1372  // Sender
1373  //$out.= "Sender: ".getValidAddress($this->addr_from,2)).$this->eol2;
1374  $out .= "From: ".$this->getValidAddress($this->addr_from, 3, 1).$this->eol2;
1375  if (!empty($conf->global->MAIN_MAIL_SENDMAIL_FORCE_BA)) {
1376  $out .= "To: ".$this->getValidAddress($this->addr_to, 0, 1).$this->eol2;
1377  }
1378  // Return-Path is important because it is used by SPF. Some MTA does not read Return-Path from header but from command line. See option MAIN_MAIL_ALLOW_SENDMAIL_F for that.
1379  $out .= "Return-Path: ".$this->getValidAddress($this->addr_from, 0, 1).$this->eol2;
1380  if (isset($this->reply_to) && $this->reply_to) {
1381  $out .= "Reply-To: ".$this->getValidAddress($this->reply_to, 2).$this->eol2;
1382  }
1383  if (isset($this->errors_to) && $this->errors_to) {
1384  $out .= "Errors-To: ".$this->getValidAddress($this->errors_to, 2).$this->eol2;
1385  }
1386 
1387  // Receiver
1388  if (isset($this->addr_cc) && $this->addr_cc) {
1389  $out .= "Cc: ".$this->getValidAddress($this->addr_cc, 2).$this->eol2;
1390  }
1391  if (isset($this->addr_bcc) && $this->addr_bcc) {
1392  $out .= "Bcc: ".$this->getValidAddress($this->addr_bcc, 2).$this->eol2; // TODO Question: bcc must not be into header, only into SMTP command "RCPT TO". Does php mail support this ?
1393  }
1394 
1395  // Delivery receipt
1396  if (isset($this->deliveryreceipt) && $this->deliveryreceipt == 1) {
1397  $out .= "Disposition-Notification-To: ".$this->getValidAddress($this->addr_from, 2).$this->eol2;
1398  }
1399 
1400  //$out.= "X-Priority: 3".$this->eol2;
1401 
1402  $out .= 'Date: '.date("r").$this->eol2;
1403 
1404  $trackid = $this->trackid;
1405  if ($trackid) {
1406  // References is kept in response and Message-ID is returned into In-Reply-To:
1407  $this->msgid = time().'.phpmail-dolibarr-'.$trackid.'@'.$host;
1408  $out .= 'Message-ID: <'.$this->msgid.">".$this->eol2; // Uppercase seems replaced by phpmail
1409  $out .= 'References: <'.$this->msgid.">".$this->eol2;
1410  $out .= 'X-Dolibarr-TRACKID: '.$trackid.'@'.$host.$this->eol2;
1411  } else {
1412  $this->msgid = time().'.phpmail@'.$host;
1413  $out .= 'Message-ID: <'.$this->msgid.">".$this->eol2;
1414  }
1415 
1416  if (!empty($_SERVER['REMOTE_ADDR'])) {
1417  $out .= "X-RemoteAddr: ".$_SERVER['REMOTE_ADDR'].$this->eol2;
1418  }
1419  $out .= "X-Mailer: Dolibarr version ".DOL_VERSION." (using php mail)".$this->eol2;
1420  $out .= "Mime-Version: 1.0".$this->eol2;
1421 
1422  //$out.= "From: ".$this->getValidAddress($this->addr_from,3,1).$this->eol;
1423 
1424  $out .= "Content-Type: multipart/mixed;".$this->eol2." boundary=\"".$this->mixed_boundary."\"".$this->eol2;
1425  $out .= "Content-Transfer-Encoding: 8bit".$this->eol2; // TODO Seems to be ignored. Header is 7bit once received.
1426 
1427  dol_syslog("CMailFile::write_smtpheaders smtp_header=\n".$out);
1428  return $out;
1429  }
1430 
1431 
1432  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1440  public function write_mimeheaders($filename_list, $mimefilename_list)
1441  {
1442  // phpcs:enable
1443  $mimedone = 0;
1444  $out = "";
1445 
1446  if (is_array($filename_list)) {
1447  $filename_list_size = count($filename_list);
1448  for ($i = 0; $i < $filename_list_size; $i++) {
1449  if ($filename_list[$i]) {
1450  if ($mimefilename_list[$i]) {
1451  $filename_list[$i] = $mimefilename_list[$i];
1452  }
1453  $out .= "X-attachments: $filename_list[$i]".$this->eol2;
1454  }
1455  }
1456  }
1457 
1458  dol_syslog("CMailFile::write_mimeheaders mime_header=\n".$out, LOG_DEBUG);
1459  return $out;
1460  }
1461 
1462  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1469  public function write_body($msgtext)
1470  {
1471  // phpcs:enable
1472  global $conf;
1473 
1474  $out = '';
1475 
1476  $out .= "--".$this->mixed_boundary.$this->eol;
1477 
1478  if ($this->atleastoneimage) {
1479  $out .= "Content-Type: multipart/alternative;".$this->eol." boundary=\"".$this->alternative_boundary."\"".$this->eol;
1480  $out .= $this->eol;
1481  $out .= "--".$this->alternative_boundary.$this->eol;
1482  }
1483 
1484  // Make RFC821 Compliant, replace bare linefeeds
1485  $strContent = preg_replace("/(?<!\r)\n/si", "\r\n", $msgtext); // PCRE modifier /s means new lines are common chars
1486  if (!empty($conf->global->MAIN_FIX_FOR_BUGGED_MTA)) {
1487  $strContent = preg_replace("/\r\n/si", "\n", $strContent); // PCRE modifier /s means new lines are common chars
1488  }
1489 
1490  $strContentAltText = '';
1491  if ($this->msgishtml) {
1492  // Similar code to forge a text from html is also in smtps.class.php
1493  $strContentAltText = preg_replace("/<br\s*[^>]*>/", " ", $strContent);
1494  // TODO We could replace <img ...> with [Filename.ext] like Gmail do.
1495  $strContentAltText = html_entity_decode(strip_tags($strContentAltText)); // Remove any HTML tags
1496  $strContentAltText = trim(wordwrap($strContentAltText, 75, empty($conf->global->MAIN_FIX_FOR_BUGGED_MTA) ? "\r\n" : "\n"));
1497 
1498  // Check if html header already in message, if not complete the message
1499  $strContent = $this->checkIfHTML($strContent);
1500  }
1501 
1502  // Make RFC2045 Compliant, split lines
1503  //$strContent = rtrim(chunk_split($strContent)); // Function chunck_split seems ko if not used on a base64 content
1504  // TODO Encode main content into base64 and use the chunk_split, or quoted-printable
1505  $strContent = rtrim(wordwrap($strContent, 75, empty($conf->global->MAIN_FIX_FOR_BUGGED_MTA) ? "\r\n" : "\n")); // TODO Using this method creates unexpected line break on text/plain content.
1506 
1507  if ($this->msgishtml) {
1508  if ($this->atleastoneimage) {
1509  $out .= "Content-Type: text/plain; charset=".$conf->file->character_set_client.$this->eol;
1510  //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1511  $out .= $this->eol.($strContentAltText ? $strContentAltText : strip_tags($strContent)).$this->eol; // Add plain text message
1512  $out .= "--".$this->alternative_boundary.$this->eol;
1513  $out .= "Content-Type: multipart/related;".$this->eol." boundary=\"".$this->related_boundary."\"".$this->eol;
1514  $out .= $this->eol;
1515  $out .= "--".$this->related_boundary.$this->eol;
1516  }
1517 
1518  if (!$this->atleastoneimage && $strContentAltText && !empty($conf->global->MAIN_MAIL_USE_MULTI_PART)) { // Add plain text message part before html part
1519  $out .= "Content-Type: multipart/alternative;".$this->eol." boundary=\"".$this->alternative_boundary."\"".$this->eol;
1520  $out .= $this->eol;
1521  $out .= "--".$this->alternative_boundary.$this->eol;
1522  $out .= "Content-Type: text/plain; charset=".$conf->file->character_set_client.$this->eol;
1523  //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1524  $out .= $this->eol.$strContentAltText.$this->eol;
1525  $out .= "--".$this->alternative_boundary.$this->eol;
1526  }
1527 
1528  $out .= "Content-Type: text/html; charset=".$conf->file->character_set_client.$this->eol;
1529  //$out.= "Content-Transfer-Encoding: 7bit".$this->eol; // TODO Use base64
1530  $out .= $this->eol.$strContent.$this->eol;
1531 
1532  if (!$this->atleastoneimage && $strContentAltText && !empty($conf->global->MAIN_MAIL_USE_MULTI_PART)) { // Add plain text message part after html part
1533  $out .= "--".$this->alternative_boundary."--".$this->eol;
1534  }
1535  } else {
1536  $out .= "Content-Type: text/plain; charset=".$conf->file->character_set_client.$this->eol;
1537  //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1538  $out .= $this->eol.$strContent.$this->eol;
1539  }
1540 
1541  $out .= $this->eol;
1542 
1543  // Encode images
1544  if ($this->atleastoneimage) {
1545  $out .= $this->write_images($this->images_encoded);
1546  // always end related and end alternative after inline images
1547  $out .= "--".$this->related_boundary."--".$this->eol;
1548  $out .= $this->eol."--".$this->alternative_boundary."--".$this->eol;
1549  $out .= $this->eol;
1550  }
1551 
1552  return $out;
1553  }
1554 
1555  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1565  private function write_files($filename_list, $mimetype_list, $mimefilename_list, $cidlist)
1566  {
1567  // phpcs:enable
1568  $out = '';
1569 
1570  $filename_list_size = count($filename_list);
1571  for ($i = 0; $i < $filename_list_size; $i++) {
1572  if ($filename_list[$i]) {
1573  dol_syslog("CMailFile::write_files: i=$i");
1574  $encoded = $this->_encode_file($filename_list[$i]);
1575  if ($encoded >= 0) {
1576  if ($mimefilename_list[$i]) {
1577  $filename_list[$i] = $mimefilename_list[$i];
1578  }
1579  if (!$mimetype_list[$i]) {
1580  $mimetype_list[$i] = "application/octet-stream";
1581  }
1582 
1583  $out .= "--".$this->mixed_boundary.$this->eol;
1584  $out .= "Content-Disposition: attachment; filename=\"".$filename_list[$i]."\"".$this->eol;
1585  $out .= "Content-Type: ".$mimetype_list[$i]."; name=\"".$filename_list[$i]."\"".$this->eol;
1586  $out .= "Content-Transfer-Encoding: base64".$this->eol;
1587  $out .= "Content-Description: ".$filename_list[$i].$this->eol;
1588  if (!empty($cidlist) && is_array($cidlist) && $cidlist[$i]) {
1589  $out .= "X-Attachment-Id: ".$cidlist[$i].$this->eol;
1590  $out .= "Content-ID: <".$cidlist[$i].'>'.$this->eol;
1591  }
1592  $out .= $this->eol;
1593  $out .= $encoded;
1594  $out .= $this->eol;
1595  //$out.= $this->eol;
1596  } else {
1597  return $encoded;
1598  }
1599  }
1600  }
1601 
1602  return $out;
1603  }
1604 
1605 
1606  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1613  public function write_images($images_list)
1614  {
1615  // phpcs:enable
1616  $out = '';
1617 
1618  if (is_array($images_list)) {
1619  foreach ($images_list as $img) {
1620  dol_syslog("CMailFile::write_images: ".$img["name"]);
1621 
1622  $out .= "--".$this->related_boundary.$this->eol; // always related for an inline image
1623  $out .= "Content-Type: ".$img["content_type"]."; name=\"".$img["name"]."\"".$this->eol;
1624  $out .= "Content-Transfer-Encoding: base64".$this->eol;
1625  $out .= "Content-Disposition: inline; filename=\"".$img["name"]."\"".$this->eol;
1626  $out .= "Content-ID: <".$img["cid"].">".$this->eol;
1627  $out .= $this->eol;
1628  $out .= $img["image_encoded"];
1629  $out .= $this->eol;
1630  }
1631  }
1632 
1633  return $out;
1634  }
1635 
1636 
1637  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1645  public function check_server_port($host, $port)
1646  {
1647  // phpcs:enable
1648  global $conf;
1649 
1650  $_retVal = 0;
1651  $timeout = 5; // Timeout in seconds
1652 
1653  if (function_exists('fsockopen')) {
1654  $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER';
1655  $keyforsmtpport = 'MAIN_MAIL_SMTP_PORT';
1656  $keyforsmtpid = 'MAIN_MAIL_SMTPS_ID';
1657  $keyforsmtppw = 'MAIN_MAIL_SMTPS_PW';
1658  $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE';
1659  $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE';
1660  $keyfortls = 'MAIN_MAIL_EMAIL_TLS';
1661  $keyforstarttls = 'MAIN_MAIL_EMAIL_STARTTLS';
1662  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
1663 
1664  if (!empty($this->sendcontext)) {
1665  $smtpContextKey = strtoupper($this->sendcontext);
1666  $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_'.$smtpContextKey);
1667  if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
1668  $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER_'.$smtpContextKey;
1669  $keyforsmtpport = 'MAIN_MAIL_SMTP_PORT_'.$smtpContextKey;
1670  $keyforsmtpid = 'MAIN_MAIL_SMTPS_ID_'.$smtpContextKey;
1671  $keyforsmtppw = 'MAIN_MAIL_SMTPS_PW_'.$smtpContextKey;
1672  $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE_'.$smtpContextKey;
1673  $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE_'.$smtpContextKey;
1674  $keyfortls = 'MAIN_MAIL_EMAIL_TLS_'.$smtpContextKey;
1675  $keyforstarttls = 'MAIN_MAIL_EMAIL_STARTTLS_'.$smtpContextKey;
1676  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_'.$smtpContextKey;
1677  }
1678  }
1679 
1680  // If we use SSL/TLS
1681  if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
1682  $host = 'ssl://'.$host;
1683  }
1684  // tls smtp start with no encryption
1685  //if (!empty($conf->global->MAIN_MAIL_EMAIL_STARTTLS) && function_exists('openssl_open')) $host='tls://'.$host;
1686 
1687  dol_syslog("Try socket connection to host=".$host." port=".$port);
1688  //See if we can connect to the SMTP server
1689  $errno = 0; $errstr = '';
1690  if ($socket = @fsockopen(
1691  $host, // Host to test, IP or domain. Add ssl:// for SSL/TLS.
1692  $port, // which Port number to use
1693  $errno, // actual system level error
1694  $errstr, // and any text that goes with the error
1695  $timeout // timeout for reading/writing data over the socket
1696  )) {
1697  // Windows still does not have support for this timeout function
1698  if (function_exists('stream_set_timeout')) {
1699  stream_set_timeout($socket, $timeout, 0);
1700  }
1701 
1702  dol_syslog("Now we wait for answer 220");
1703 
1704  // Check response from Server
1705  if ($_retVal = $this->server_parse($socket, "220")) {
1706  $_retVal = $socket;
1707  }
1708  } else {
1709  $this->error = utf8_check('Error '.$errno.' - '.$errstr) ? 'Error '.$errno.' - '.$errstr : utf8_encode('Error '.$errno.' - '.$errstr);
1710  }
1711  }
1712  return $_retVal;
1713  }
1714 
1715  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1724  public function server_parse($socket, $response)
1725  {
1726  // phpcs:enable
1727  $_retVal = true; // Indicates if Object was created or not
1728  $server_response = '';
1729 
1730  while (substr($server_response, 3, 1) != ' ') {
1731  if (!($server_response = fgets($socket, 256))) {
1732  $this->error = "Couldn't get mail server response codes";
1733  return false;
1734  }
1735  }
1736 
1737  if (!(substr($server_response, 0, 3) == $response)) {
1738  $this->error = "Ran into problems sending Mail.\r\nResponse: $server_response";
1739  $_retVal = false;
1740  }
1741 
1742  return $_retVal;
1743  }
1744 
1751  private function findHtmlImages($images_dir)
1752  {
1753  // Build the array of image extensions
1754  $extensions = array_keys($this->image_types);
1755 
1756  // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
1757  // For example when:
1758  // <img alt="" src="/viewimage.php?modulepart=medias&amp;entity=1&amp;file=image/picture.jpg" style="height:356px; width:1040px" />
1759  $matches = array();
1760  preg_match_all('/(?:"|\')([^"\']+\.('.implode('|', $extensions).'))(?:"|\')/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
1761 
1762  if (!empty($matches) && !empty($matches[1])) {
1763  $i = 0;
1764  // We are interested in $matches[1] only (the second set of parenthesis into regex)
1765  foreach ($matches[1] as $full) {
1766  $regs = array();
1767  if (preg_match('/file=([A-Za-z0-9_\-\/]+[\.]?[A-Za-z0-9]+)?$/i', $full, $regs)) { // If xxx is 'file=aaa'
1768  $img = $regs[1];
1769 
1770  if (file_exists($images_dir.'/'.$img)) {
1771  // Image path in src
1772  $src = preg_quote($full, '/');
1773  // Image full path
1774  $this->html_images[$i]["fullpath"] = $images_dir.'/'.$img;
1775  // Image name
1776  $this->html_images[$i]["name"] = $img;
1777  // Content type
1778  $regext = array();
1779  if (preg_match('/^.+\.(\w{3,4})$/', $img, $regext)) {
1780  $ext = strtolower($regext[1]);
1781  $this->html_images[$i]["content_type"] = $this->image_types[$ext];
1782  }
1783  // cid
1784  $this->html_images[$i]["cid"] = dol_hash($this->html_images[$i]["fullpath"], 'md5'); // Force md5 hash (does not contains special chars)
1785  // type
1786  $this->html_images[$i]["type"] = 'cidfromurl';
1787 
1788  $this->html = preg_replace("/src=\"$src\"|src='$src'/i", "src=\"cid:".$this->html_images[$i]["cid"]."\"", $this->html);
1789  }
1790  $i++;
1791  }
1792  }
1793 
1794  if (!empty($this->html_images)) {
1795  $inline = array();
1796 
1797  $i = 0;
1798 
1799  foreach ($this->html_images as $img) {
1800  $fullpath = $images_dir.'/'.$img["name"];
1801 
1802  // If duplicate images are embedded, they may show up as attachments, so remove them.
1803  if (!in_array($fullpath, $inline)) {
1804  // Read image file
1805  if ($image = file_get_contents($fullpath)) {
1806  // On garde que le nom de l'image
1807  $regs = array();
1808  preg_match('/([A-Za-z0-9_-]+[\.]?[A-Za-z0-9]+)?$/i', $img["name"], $regs);
1809  $imgName = $regs[1];
1810 
1811  $this->images_encoded[$i]['name'] = $imgName;
1812  $this->images_encoded[$i]['fullpath'] = $fullpath;
1813  $this->images_encoded[$i]['content_type'] = $img["content_type"];
1814  $this->images_encoded[$i]['cid'] = $img["cid"];
1815  // Encodage de l'image
1816  $this->images_encoded[$i]["image_encoded"] = chunk_split(base64_encode($image), 68, $this->eol);
1817  $inline[] = $fullpath;
1818  }
1819  }
1820  $i++;
1821  }
1822  } else {
1823  return -1;
1824  }
1825 
1826  return 1;
1827  } else {
1828  return 0;
1829  }
1830  }
1831 
1839  private function findHtmlImagesIsSrcData($images_dir)
1840  {
1841  global $conf;
1842 
1843  // Build the array of image extensions
1844  $extensions = array_keys($this->image_types);
1845 
1846  if ($images_dir && !dol_is_dir($images_dir)) {
1847  dol_mkdir($images_dir, DOL_DATA_ROOT);
1848  }
1849 
1850  // Uncomment this for debug
1851  /*
1852  global $dolibarr_main_data_root;
1853  $outputfile = $dolibarr_main_data_root."/dolibarr_mail.log";
1854  $fp = fopen($outputfile, "w+");
1855  fwrite($fp, $this->html);
1856  fclose($fp);
1857  */
1858 
1859  // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
1860  // For example when:
1861  // <img alt="" src="/src="data:image....;base64,...." />
1862  $matches = array();
1863  preg_match_all('/src="data:image\/('.implode('|', $extensions).');base64,([^"]+)"/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
1864 
1865  if (!empty($matches) && !empty($matches[1])) {
1866  if (empty($images_dir)) {
1867  // No temp directory provided, so we are not able to support convertion of data:image into physical images.
1868  $this->error = 'NoTempDirProvidedInCMailConstructorSoCantConvertDataImgOnDisk';
1869  return -1;
1870  }
1871 
1872  $i = count($this->html_images);
1873  foreach ($matches[1] as $key => $ext) {
1874  // We save the image to send in disk
1875  $filecontent = $matches[2][$key];
1876 
1877  $cid = 'cid000'.dol_hash($filecontent, 'md5'); // The id must not change if image is same
1878 
1879  $destfiletmp = $images_dir.'/'.$cid.'.'.$ext;
1880 
1881  if (!dol_is_file($destfiletmp)) { // If file does not exist yet (this is the case for the first email sent with a data:image inside)
1882  dol_syslog("write the cid file ".$destfiletmp);
1883  $fhandle = @fopen($destfiletmp, 'w');
1884  if ($fhandle) {
1885  $nbofbyteswrote = fwrite($fhandle, base64_decode($filecontent));
1886  fclose($fhandle);
1887  @chmod($destfiletmp, octdec($conf->global->MAIN_UMASK));
1888  } else {
1889  $this->errors[] = "Failed to open file '".$destfiletmp."' for write";
1890  return -1;
1891  }
1892  }
1893 
1894  if (file_exists($destfiletmp)) {
1895  // Image full path
1896  $this->html_images[$i]["fullpath"] = $destfiletmp;
1897  // Image name
1898  $this->html_images[$i]["name"] = basename($destfiletmp);
1899  // Content type
1900  $this->html_images[$i]["content_type"] = $this->image_types[strtolower($ext)];
1901  // cid
1902  $this->html_images[$i]["cid"] = $cid;
1903  // type
1904  $this->html_images[$i]["type"] = 'cidfromdata';
1905 
1906  $this->html = str_replace('src="data:image/'.$ext.';base64,'.$filecontent.'"', 'src="cid:'.$this->html_images[$i]["cid"].'"', $this->html);
1907  }
1908  $i++;
1909  }
1910 
1911  return 1;
1912  } else {
1913  return 0;
1914  }
1915  }
1916 
1932  public static function getValidAddress($address, $format, $encode = 0, $maxnumberofemail = 0)
1933  {
1934  global $conf;
1935 
1936  $ret = '';
1937 
1938  $arrayaddress = explode(',', $address);
1939 
1940  // Boucle sur chaque composant de l'adresse
1941  $i = 0;
1942  foreach ($arrayaddress as $val) {
1943  $regs = array();
1944  if (preg_match('/^(.*)<(.*)>$/i', trim($val), $regs)) {
1945  $name = trim($regs[1]);
1946  $email = trim($regs[2]);
1947  } else {
1948  $name = '';
1949  $email = trim($val);
1950  }
1951 
1952  if ($email) {
1953  $i++;
1954 
1955  $newemail = '';
1956  if ($format == 5) {
1957  $newemail = $name ? $name : $email;
1958  $newemail = '<a href="mailto:'.$email.'">'.$newemail.'</a>';
1959  }
1960  if ($format == 4) {
1961  $newemail = $name ? $name : $email;
1962  }
1963  if ($format == 2) {
1964  $newemail = $email;
1965  }
1966  if ($format == 1 || $format == 3) {
1967  $newemail = '<'.$email.'>';
1968  }
1969  if ($format == 0 || $format == 3) {
1970  if (!empty($conf->global->MAIN_MAIL_NO_FULL_EMAIL)) {
1971  $newemail = '<'.$email.'>';
1972  } elseif (!$name) {
1973  $newemail = '<'.$email.'>';
1974  } else {
1975  $newemail = ($format == 3 ? '"' : '').($encode ?self::encodetorfc2822($name) : $name).($format == 3 ? '"' : '').' <'.$email.'>';
1976  }
1977  }
1978 
1979  $ret = ($ret ? $ret.',' : '').$newemail;
1980 
1981  // Stop if we have too much records
1982  if ($maxnumberofemail && $i >= $maxnumberofemail) {
1983  if (count($arrayaddress) > $maxnumberofemail) {
1984  $ret .= '...';
1985  }
1986  break;
1987  }
1988  }
1989  }
1990 
1991  return $ret;
1992  }
1993 
2001  public static function getArrayAddress($address)
2002  {
2003  global $conf;
2004 
2005  $ret = array();
2006 
2007  $arrayaddress = explode(',', $address);
2008 
2009  // Boucle sur chaque composant de l'adresse
2010  foreach ($arrayaddress as $val) {
2011  if (preg_match('/^(.*)<(.*)>$/i', trim($val), $regs)) {
2012  $name = trim($regs[1]);
2013  $email = trim($regs[2]);
2014  } else {
2015  $name = null;
2016  $email = trim($val);
2017  }
2018 
2019  $ret[$email] = empty($conf->global->MAIN_MAIL_NO_FULL_EMAIL) ? $name : null;
2020  }
2021 
2022  return $ret;
2023  }
2024 }
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
_encode_file($sourcefile)
Read a file on disk and return encoded content for emails (mode = 'mail')
write_body($msgtext)
Return email content (mode = 'mail')
$bodyCSS
Defined background directly in body tag.
dump_mail()
Write content of a SMTP request into a dump file (mode = all) Used for debugging.
sendfile()
Send mail that was prepared by constructor.
static encodetorfc2822($stringtoencode)
Encode subject according to RFC 2822 - http://en.wikipedia.org/wiki/MIME#Encoded-Word.
checkIfHTML($msg)
Correct an uncomplete html string.
save_dump_mail_in_err()
Save content if mail is in error Used for debugging.
static getValidAddress($address, $format, $encode=0, $maxnumberofemail=0)
Return a formatted address string for SMTP protocol.
write_images($images_list)
Attach an image to email (mode = 'mail')
server_parse($socket, $response)
This function has been modified as provided by SirSir to allow multiline responses when using SMTP Ex...
__construct($subject, $to, $from, $msg, $filename_list=array(), $mimetype_list=array(), $mimefilename_list=array(), $addr_cc="", $addr_bcc="", $deliveryreceipt=0, $msgishtml=0, $errors_to='', $css='', $trackid='', $moreinheader='', $sendcontext='standard', $replyto='', $upload_dir_tmp='')
CMailFile.
write_smtpheaders()
Create SMTP headers (mode = 'mail')
findHtmlImagesIsSrcData($images_dir)
Seearch images with data:image format into html message.
$styleCSS
Defined css style for body background.
write_mimeheaders($filename_list, $mimefilename_list)
Create header MIME (mode = 'mail')
check_server_port($host, $port)
Try to create a socket connection.
buildCSS()
Build a css style (mode = all) into this->styleCSS and this->bodyCSS.
write_files($filename_list, $mimetype_list, $mimefilename_list, $cidlist)
Attach file to email (mode = 'mail')
static getArrayAddress($address)
Return a formatted array of address string for SMTP protocol.
findHtmlImages($images_dir)
Search images into html message and init array this->images_encoded if found.
Class to manage hooks.
Class to construct and send SMTP compliant email, even to a secure SMTP server, regardless of platfor...
Definition: smtps.class.php:47
dol_is_file($pathoffile)
Return if path is a file.
Definition: files.lib.php:481
dol_is_dir($folder)
Test if filename is a directory.
Definition: files.lib.php:451
dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1, $testvirus=0, $indexdatabase=1)
Move a file into another name.
Definition: files.lib.php:875
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
dol_nl2br($stringtoencode, $nl2brmode=0, $forxml=false)
Replace CRLF in string with a HTML BR tag.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dol_now($mode='auto')
Return date for now.
dol_string_unaccent($str)
Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName.
ascii_check($str)
Check if a string is in ASCII.
dol_textishtml($msg, $option=0)
Return if a text is a html content.
if(!function_exists('utf8_encode')) if(!function_exists('utf8_decode')) getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
utf8_check($str)
Check if a string is in UTF8.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
dol_sanitizeEmail($stringtoclean)
Clean a string to use it as an Email.
dol_hash($chain, $type='0')
Returns a hash (non reversible encryption) of a string.