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