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