dolibarr  18.0.0
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', 'swiftmailer', ...) according to $sendcontext ('standard', 'emailing', 'ticket', 'password')
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 contain special chars)
218 
219  // On defini alternative_boundary
220  $this->alternative_boundary = 'mul_'.dol_hash(uniqid("dolibarr3"), 3); // Force md5 hash (does not contain 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  $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  if (!empty($this->errors_to)) {
561  try {
562  $headers->addTextHeader('Errors-To', $this->getArrayAddress($this->errors_to));
563  } catch (Exception $e) {
564  $this->errors[] = $e->getMessage();
565  }
566  }
567 
568  try {
569  $this->message->setCharSet($conf->file->character_set_client);
570  } catch (Exception $e) {
571  $this->errors[] = $e->getMessage();
572  }
573 
574  if (!empty($this->html)) {
575  if (!empty($css)) {
576  $this->css = $css;
577  $this->buildCSS();
578  }
579  $msg = $this->html;
580  $msg = $this->checkIfHTML($msg);
581  }
582 
583  if ($this->atleastoneimage) {
584  foreach ($this->images_encoded as $img) {
585  //$img['fullpath'],$img['image_encoded'],$img['name'],$img['content_type'],$img['cid']
586  $attachment = Swift_Image::fromPath($img['fullpath']);
587  // embed image
588  $imgcid = $this->message->embed($attachment);
589  // replace cid by the one created by swiftmail in html message
590  $msg = str_replace("cid:".$img['cid'], $imgcid, $msg);
591  }
592  }
593 
594  if ($this->msgishtml) {
595  $this->message->setBody($msg, 'text/html');
596  // And optionally an alternative body
597  $this->message->addPart(html_entity_decode(strip_tags($msg)), 'text/plain');
598  } else {
599  $this->message->setBody($msg, 'text/plain');
600  // And optionally an alternative body
601  $this->message->addPart(dol_nl2br($msg), 'text/html');
602  }
603 
604  if (!empty($this->atleastonefile)) {
605  foreach ($filename_list as $i => $val) {
606  //$this->message->attach(Swift_Attachment::fromPath($filename_list[$i],$mimetype_list[$i]));
607  $attachment = Swift_Attachment::fromPath($filename_list[$i], $mimetype_list[$i]);
608  if (!empty($mimefilename_list[$i])) {
609  $attachment->setFilename($mimefilename_list[$i]);
610  }
611  $this->message->attach($attachment);
612  }
613  }
614 
615  if (!empty($this->addr_cc)) {
616  try {
617  $this->message->setCc($this->getArrayAddress($this->addr_cc));
618  } catch (Exception $e) {
619  $this->errors[] = $e->getMessage();
620  }
621  }
622  if (!empty($this->addr_bcc)) {
623  try {
624  $this->message->setBcc($this->getArrayAddress($this->addr_bcc));
625  } catch (Exception $e) {
626  $this->errors[] = $e->getMessage();
627  }
628  }
629  //if (!empty($this->errors_to)) $this->message->setErrorsTo($this->getArrayAddress($this->errors_to));
630  if (isset($this->deliveryreceipt) && $this->deliveryreceipt == 1) {
631  try {
632  $this->message->setReadReceiptTo($this->getArrayAddress($this->addr_from));
633  } catch (Exception $e) {
634  $this->errors[] = $e->getMessage();
635  }
636  }
637  } else {
638  // Send mail method not correctly defined
639  // --------------------------------------
640  $this->error = 'Bad value for sendmode';
641  }
642  }
643 
644 
650  public function sendfile()
651  {
652  global $conf, $db, $langs, $hookmanager;
653 
654  $errorlevel = error_reporting();
655  //error_reporting($errorlevel ^ E_WARNING); // Desactive warnings
656 
657  $res = false;
658 
659  if (empty($conf->global->MAIN_DISABLE_ALL_MAILS)) {
660  if (!is_object($hookmanager)) {
661  include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
662  $hookmanager = new HookManager($db);
663  }
664  $hookmanager->initHooks(array('mail'));
665 
666  $parameters = array();
667  $action = '';
668  $reshook = $hookmanager->executeHooks('sendMail', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
669  if ($reshook < 0) {
670  $this->error = "Error in hook maildao sendMail ".$reshook;
671  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
672 
673  return $reshook;
674  }
675  if ($reshook == 1) { // Hook replace standard code
676  return true;
677  }
678 
679  $sendingmode = $this->sendmode;
680  if ($this->sendcontext == 'emailing' && !empty($conf->global->MAILING_NO_USING_PHPMAIL) && $sendingmode == 'mail') {
681  // List of sending methods
682  $listofmethods = array();
683  $listofmethods['mail'] = 'PHP mail function';
684  //$listofmethods['simplemail']='Simplemail class';
685  $listofmethods['smtps'] = 'SMTP/SMTPS socket library';
686 
687  // EMailing feature may be a spam problem, so when you host several users/instance, having this option may force each user to use their own SMTP agent.
688  // You ensure that every user is using its own SMTP server when using the mass emailing module.
689  $linktoadminemailbefore = '<a href="'.DOL_URL_ROOT.'/admin/mails.php">';
690  $linktoadminemailend = '</a>';
691  $this->error = $langs->trans("MailSendSetupIs", $listofmethods[$sendingmode]);
692  $this->errors[] = $langs->trans("MailSendSetupIs", $listofmethods[$sendingmode]);
693  $this->error .= '<br>'.$langs->trans("MailSendSetupIs2", $linktoadminemailbefore, $linktoadminemailend, $langs->transnoentitiesnoconv("MAIN_MAIL_SENDMODE"), $listofmethods['smtps']);
694  $this->errors[] = $langs->trans("MailSendSetupIs2", $linktoadminemailbefore, $linktoadminemailend, $langs->transnoentitiesnoconv("MAIN_MAIL_SENDMODE"), $listofmethods['smtps']);
695  if (!empty($conf->global->MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS)) {
696  $this->error .= '<br>'.$langs->trans("MailSendSetupIs3", $conf->global->MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS);
697  $this->errors[] = $langs->trans("MailSendSetupIs3", $conf->global->MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS);
698  }
699 
700  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_WARNING);
701  return false;
702  }
703 
704  // Check number of recipient is lower or equal than MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL
705  if (empty($conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL)) {
706  $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL = 10;
707  }
708  $tmparray1 = explode(',', $this->addr_to);
709  if (count($tmparray1) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL) {
710  $this->error = 'Too much recipients in to:';
711  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_WARNING);
712  return false;
713  }
714  if (empty($conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL)) {
715  $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL = 10;
716  }
717  $tmparray2 = explode(',', $this->addr_cc);
718  if (count($tmparray2) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL) {
719  $this->error = 'Too much recipients in cc:';
720  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_WARNING);
721  return false;
722  }
723  if (empty($conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL)) {
724  $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL = 10;
725  }
726  $tmparray3 = explode(',', $this->addr_bcc);
727  if (count($tmparray3) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL) {
728  $this->error = 'Too much recipients in bcc:';
729  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_WARNING);
730  return false;
731  }
732  if (empty($conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL)) {
733  $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL = 10;
734  }
735  if ((count($tmparray1) + count($tmparray2) + count($tmparray3)) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL) {
736  $this->error = 'Too much recipients in to:, cc:, bcc:';
737  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_WARNING);
738  return false;
739  }
740 
741  $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER';
742  $keyforsmtpport = 'MAIN_MAIL_SMTP_PORT';
743  $keyforsmtpid = 'MAIN_MAIL_SMTPS_ID';
744  $keyforsmtppw = 'MAIN_MAIL_SMTPS_PW';
745  $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE';
746  $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE';
747  $keyfortls = 'MAIN_MAIL_EMAIL_TLS';
748  $keyforstarttls = 'MAIN_MAIL_EMAIL_STARTTLS';
749  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
750  if (!empty($this->sendcontext)) {
751  $smtpContextKey = strtoupper($this->sendcontext);
752  $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_'.$smtpContextKey);
753  if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
754  $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER_'.$smtpContextKey;
755  $keyforsmtpport = 'MAIN_MAIL_SMTP_PORT_'.$smtpContextKey;
756  $keyforsmtpid = 'MAIN_MAIL_SMTPS_ID_'.$smtpContextKey;
757  $keyforsmtppw = 'MAIN_MAIL_SMTPS_PW_'.$smtpContextKey;
758  $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE_'.$smtpContextKey;
759  $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE_'.$smtpContextKey;
760  $keyfortls = 'MAIN_MAIL_EMAIL_TLS_'.$smtpContextKey;
761  $keyforstarttls = 'MAIN_MAIL_EMAIL_STARTTLS_'.$smtpContextKey;
762  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_'.$smtpContextKey;
763  }
764  }
765 
766  // Action according to choosed sending method
767  if ($this->sendmode == 'mail') {
768  // Use mail php function (default PHP method)
769  // ------------------------------------------
770  dol_syslog("CMailFile::sendfile addr_to=".$this->addr_to.", subject=".$this->subject, LOG_DEBUG);
771  //dol_syslog("CMailFile::sendfile header=\n".$this->headers, LOG_DEBUG);
772  //dol_syslog("CMailFile::sendfile message=\n".$message);
773 
774  // If Windows, sendmail_from must be defined
775  if (isset($_SERVER["WINDIR"])) {
776  if (empty($this->addr_from)) {
777  $this->addr_from = 'robot@example.com';
778  }
779  @ini_set('sendmail_from', $this->getValidAddress($this->addr_from, 2));
780  }
781 
782  // Force parameters
783  //dol_syslog("CMailFile::sendfile conf->global->".$keyforsmtpserver."=".$conf->global->$keyforsmtpserver." cpnf->global->".$keyforsmtpport."=".$conf->global->$keyforsmtpport, LOG_DEBUG);
784  if (!empty($conf->global->$keyforsmtpserver)) {
785  ini_set('SMTP', $conf->global->$keyforsmtpserver);
786  }
787  if (!empty($conf->global->$keyforsmtpport)) {
788  ini_set('smtp_port', $conf->global->$keyforsmtpport);
789  }
790 
791  $res = true;
792  if ($res && !$this->subject) {
793  $this->error = "Failed to send mail with php mail to HOST=".ini_get('SMTP').", PORT=".ini_get('smtp_port')."<br>Subject is empty";
794  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
795  $res = false;
796  }
797  $dest = $this->getValidAddress($this->addr_to, 2);
798  if ($res && !$dest) {
799  $this->error = "Failed to send mail with php mail to HOST=".ini_get('SMTP').", PORT=".ini_get('smtp_port')."<br>Recipient address '$dest' invalid";
800  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
801  $res = false;
802  }
803 
804  if ($res) {
805  $additionnalparam = ''; // By default
806  if (!empty($conf->global->MAIN_MAIL_ALLOW_SENDMAIL_F)) {
807  // le "Return-Path" (retour des messages bounced) dans les header ne fonctionne pas avec tous les MTA
808  // Le forcage de la valeur grace à l'option -f de sendmail est donc possible si la constante MAIN_MAIL_ALLOW_SENDMAIL_F est definie.
809  // Having this variable defined may create problems with some sendmail (option -f refused)
810  // Having this variable not defined may create problems with some other sendmail (option -f required)
811  $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) : ''));
812  }
813  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
814  $additionnalparam .= ($additionnalparam ? ' ' : '').'-ba';
815  }
816 
817  if (!empty($conf->global->MAIN_MAIL_SENDMAIL_FORCE_ADDPARAM)) {
818  $additionnalparam .= ($additionnalparam ? ' ' : '').'-U '.$additionnalparam; // Use -U to add additionnal params
819  }
820 
821  $linuxlike = 1;
822  if (preg_match('/^win/i', PHP_OS)) {
823  $linuxlike = 0;
824  }
825  if (preg_match('/^mac/i', PHP_OS)) {
826  $linuxlike = 0;
827  }
828 
829  dol_syslog("CMailFile::sendfile: mail start".($linuxlike ? '' : " HOST=".ini_get('SMTP').", PORT=".ini_get('smtp_port')).", additionnal_parameters=".$additionnalparam, LOG_DEBUG);
830 
831  $this->message = stripslashes($this->message);
832 
833  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
834  $this->dump_mail();
835  }
836 
837  // Encode subject if required.
838  $subjecttouse = $this->subject;
839  if (!ascii_check($subjecttouse)) {
840  $subjecttouse = $this->encodetorfc2822($subjecttouse);
841  }
842 
843  if (!empty($additionnalparam)) {
844  $res = mail($dest, $subjecttouse, $this->message, $this->headers, $additionnalparam);
845  } else {
846  $res = mail($dest, $subjecttouse, $this->message, $this->headers);
847  }
848 
849  if (!$res) {
850  $langs->load("errors");
851  $this->error = "Failed to send mail with php mail";
852  if (!$linuxlike) {
853  $this->error .= " to HOST=".ini_get('SMTP').", PORT=".ini_get('smtp_port'); // This values are value used only for non linuxlike systems
854  }
855  $this->error .= ".<br>";
856  $this->error .= $langs->trans("ErrorPhpMailDelivery");
857  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
858 
859  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
860  $this->save_dump_mail_in_err('Mail with topic '.$this->subject);
861  }
862  } else {
863  dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
864  }
865  }
866 
867  if (isset($_SERVER["WINDIR"])) {
868  @ini_restore('sendmail_from');
869  }
870 
871  // Restore parameters
872  if (!empty($conf->global->$keyforsmtpserver)) {
873  ini_restore('SMTP');
874  }
875  if (!empty($conf->global->$keyforsmtpport)) {
876  ini_restore('smtp_port');
877  }
878  } elseif ($this->sendmode == 'smtps') {
879  if (!is_object($this->smtps)) {
880  $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.";
881  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
882  return false;
883  }
884 
885  // Use SMTPS library
886  // ------------------------------------------
887  $this->smtps->setTransportType(0); // Only this method is coded in SMTPs library
888 
889  // Clean parameters
890  if (empty($conf->global->$keyforsmtpserver)) {
891  $conf->global->$keyforsmtpserver = ini_get('SMTP');
892  }
893  if (empty($conf->global->$keyforsmtpport)) {
894  $conf->global->$keyforsmtpport = ini_get('smtp_port');
895  }
896 
897  // If we use SSL/TLS
898  $server = $conf->global->$keyforsmtpserver;
899  $secure = '';
900  if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
901  $secure = 'ssl';
902  }
903  if (!empty($conf->global->$keyforstarttls) && function_exists('openssl_open')) {
904  $secure = 'tls';
905  }
906  $server = ($secure ? $secure.'://' : '').$server;
907 
908  $port = $conf->global->$keyforsmtpport;
909 
910  $this->smtps->setHost($server);
911  $this->smtps->setPort($port); // 25, 465...;
912 
913  $loginid = '';
914  $loginpass = '';
915  if (!empty($conf->global->$keyforsmtpid)) {
916  $loginid = $conf->global->$keyforsmtpid;
917  $this->smtps->setID($loginid);
918  }
919  if (!empty($conf->global->$keyforsmtppw)) {
920  $loginpass = $conf->global->$keyforsmtppw;
921  $this->smtps->setPW($loginpass);
922  }
923 
924  if (getDolGlobalString($keyforsmtpauthtype) === "XOAUTH2") {
925  require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // define $supportedoauth2array
926 
927  $keyforsupportedoauth2array = $conf->global->$keyforsmtpoauthservice;
928  if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
929  $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
930  } else {
931  $keyforprovider = '';
932  }
933  $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array);
934  $keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME';
935 
936  if (isset($supportedoauth2array)) {
937  $OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : ''));
938  } else {
939  $OAUTH_SERVICENAME = 'Unknown';
940  }
941 
942  require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
943 
944  $storage = new DoliStorage($db, $conf, $keyforprovider);
945  try {
946  $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
947  $expire = false;
948  // Is token expired or will token expire in the next 30 seconds
949  if (is_object($tokenobj)) {
950  $expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30));
951  }
952  // Token expired so we refresh it
953  if (is_object($tokenobj) && $expire) {
954  $credentials = new Credentials(
955  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_ID'),
956  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_SECRET'),
957  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_URLAUTHORIZE')
958  );
959  $serviceFactory = new \OAuth\ServiceFactory();
960  $oauthname = explode('-', $OAUTH_SERVICENAME);
961  // ex service is Google-Emails we need only the first part Google
962  $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array());
963  // We have to save the token because Google give it only once
964  $refreshtoken = $tokenobj->getRefreshToken();
965  $tokenobj = $apiService->refreshAccessToken($tokenobj);
966  $tokenobj->setRefreshToken($refreshtoken);
967  $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
968  }
969 
970  $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
971  if (is_object($tokenobj)) {
972  $this->smtps->setToken($tokenobj->getAccessToken());
973  } else {
974  $this->error = "Token not found";
975  }
976  } catch (Exception $e) {
977  // Return an error if token not found
978  $this->error = $e->getMessage();
979  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
980  }
981  }
982 
983  $res = true;
984  $from = $this->smtps->getFrom('org');
985  if ($res && !$from) {
986  $this->error = "Failed to send mail with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport." - Sender address '$from' invalid";
987  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
988  $res = false;
989  }
990  $dest = $this->smtps->getTo();
991  if ($res && !$dest) {
992  $this->error = "Failed to send mail with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport." - Recipient address '$dest' invalid";
993  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
994  $res = false;
995  }
996 
997  if ($res) {
998  dol_syslog("CMailFile::sendfile: sendMsg, HOST=".$server.", PORT=".$conf->global->$keyforsmtpport, LOG_DEBUG);
999 
1000  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1001  $this->smtps->setDebug(true);
1002  }
1003 
1004  $result = $this->smtps->sendMsg();
1005 
1006  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1007  $this->dump_mail();
1008  }
1009 
1010  $smtperrorcode = 0;
1011  if (! $result) {
1012  $smtperrorcode = $this->smtps->lastretval; // SMTP error code
1013  dol_syslog("CMailFile::sendfile: mail SMTP error code ".$smtperrorcode, LOG_WARNING);
1014 
1015  if ($smtperrorcode == '421') { // Try later
1016  // TODO Add a delay and try again
1017  /*
1018  dol_syslog("CMailFile::sendfile: Try later error, so we wait and we retry");
1019  sleep(2);
1020 
1021  $result = $this->smtps->sendMsg();
1022 
1023  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1024  $this->dump_mail();
1025  }
1026  */
1027  }
1028  }
1029 
1030  $result = $this->smtps->getErrors(); // applicative error code (not SMTP error code)
1031  if (empty($this->error) && empty($result)) {
1032  dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
1033  $res = true;
1034  } else {
1035  if (empty($this->error)) {
1036  $this->error = $result;
1037  }
1038  dol_syslog("CMailFile::sendfile: mail end error with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport." - ".$this->error, LOG_ERR);
1039  $res = false;
1040 
1041  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1042  $this->save_dump_mail_in_err('Mail smtp error '.$smtperrorcode.' with topic '.$this->subject);
1043  }
1044  }
1045  }
1046  } elseif ($this->sendmode == 'swiftmailer') {
1047  // Use Swift Mailer library
1048  // ------------------------------------------
1049  require_once DOL_DOCUMENT_ROOT.'/includes/swiftmailer/lib/swift_required.php';
1050 
1051  // Clean parameters
1052  if (empty($conf->global->$keyforsmtpserver)) {
1053  $conf->global->$keyforsmtpserver = ini_get('SMTP');
1054  }
1055  if (empty($conf->global->$keyforsmtpport)) {
1056  $conf->global->$keyforsmtpport = ini_get('smtp_port');
1057  }
1058 
1059  // If we use SSL/TLS
1060  $server = $conf->global->$keyforsmtpserver;
1061  $secure = '';
1062  if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
1063  $secure = 'ssl';
1064  }
1065  if (!empty($conf->global->$keyforstarttls) && function_exists('openssl_open')) {
1066  $secure = 'tls';
1067  }
1068 
1069  $this->transport = new Swift_SmtpTransport($server, $conf->global->$keyforsmtpport, $secure);
1070 
1071  if (!empty($conf->global->$keyforsmtpid)) {
1072  $this->transport->setUsername($conf->global->$keyforsmtpid);
1073  }
1074  if (!empty($conf->global->$keyforsmtppw) && getDolGlobalString($keyforsmtpauthtype) != "XOAUTH2") {
1075  $this->transport->setPassword($conf->global->$keyforsmtppw);
1076  }
1077  if (getDolGlobalString($keyforsmtpauthtype) === "XOAUTH2") {
1078  require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // define $supportedoauth2array
1079 
1080  $keyforsupportedoauth2array = getDolGlobalString($keyforsmtpoauthservice);
1081  if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
1082  $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
1083  } else {
1084  $keyforprovider = '';
1085  }
1086  $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array);
1087  $keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME';
1088 
1089  $OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : ''));
1090 
1091  require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
1092 
1093  $storage = new DoliStorage($db, $conf, $keyforprovider);
1094 
1095  try {
1096  $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
1097  $expire = false;
1098  // Is token expired or will token expire in the next 30 seconds
1099  if (is_object($tokenobj)) {
1100  $expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30));
1101  }
1102  // Token expired so we refresh it
1103  if (is_object($tokenobj) && $expire) {
1104  $credentials = new Credentials(
1105  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_ID'),
1106  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_SECRET'),
1107  getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_URLAUTHORIZE')
1108  );
1109  $serviceFactory = new \OAuth\ServiceFactory();
1110  $oauthname = explode('-', $OAUTH_SERVICENAME);
1111  // ex service is Google-Emails we need only the first part Google
1112  $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array());
1113  // We have to save the token because Google give it only once
1114  $refreshtoken = $tokenobj->getRefreshToken();
1115  $tokenobj = $apiService->refreshAccessToken($tokenobj);
1116  $tokenobj->setRefreshToken($refreshtoken);
1117  $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
1118  }
1119  if (is_object($tokenobj)) {
1120  $this->transport->setAuthMode('XOAUTH2');
1121  $this->transport->setPassword($tokenobj->getAccessToken());
1122  } else {
1123  $this->errors[] = "Token not found";
1124  }
1125  } catch (Exception $e) {
1126  // Return an error if token not found
1127  $this->errors[] = $e->getMessage();
1128  dol_syslog("CMailFile::sendfile: mail end error=".$e->getMessage(), LOG_ERR);
1129  }
1130  }
1131  if (!empty($conf->global->$keyforsslseflsigned)) {
1132  $this->transport->setStreamOptions(array('ssl' => array('allow_self_signed' => true, 'verify_peer' => false)));
1133  }
1134  //$smtps->_msgReplyTo = 'reply@web.com';
1135 
1136  // Switch content encoding to base64 - avoid the doubledot issue with quoted-printable
1137  $contentEncoderBase64 = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
1138  $this->message->setEncoder($contentEncoderBase64);
1139 
1140  // Create the Mailer using your created Transport
1141  $this->mailer = new Swift_Mailer($this->transport);
1142 
1143  // DKIM SIGN
1144  if ($conf->global->MAIN_MAIL_EMAIL_DKIM_ENABLED) {
1145  $privateKey = $conf->global->MAIN_MAIL_EMAIL_DKIM_PRIVATE_KEY;
1146  $domainName = $conf->global->MAIN_MAIL_EMAIL_DKIM_DOMAIN;
1147  $selector = $conf->global->MAIN_MAIL_EMAIL_DKIM_SELECTOR;
1148  $signer = new Swift_Signers_DKIMSigner($privateKey, $domainName, $selector);
1149  $this->message->attachSigner($signer->ignoreHeader('Return-Path'));
1150  }
1151 
1152  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1153  // To use the ArrayLogger
1154  $this->logger = new Swift_Plugins_Loggers_ArrayLogger();
1155  // Or to use the Echo Logger
1156  //$this->logger = new Swift_Plugins_Loggers_EchoLogger();
1157  $this->mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($this->logger));
1158  }
1159 
1160  dol_syslog("CMailFile::sendfile: mailer->send, HOST=".$server.", PORT=".$conf->global->$keyforsmtpport, LOG_DEBUG);
1161 
1162  // send mail
1163  $failedRecipients = array();
1164  try {
1165  $result = $this->mailer->send($this->message, $failedRecipients);
1166  } catch (Exception $e) {
1167  $this->errors[] = $e->getMessage();
1168  }
1169  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1170  $this->dump_mail();
1171  }
1172 
1173  $res = true;
1174  if (!empty($this->error) || !empty($this->errors) || !$result) {
1175  if (!empty($failedRecipients)) {
1176  $this->errors[] = 'Transport failed for the following addresses: "' . join('", "', $failedRecipients) . '".';
1177  }
1178  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
1179  $res = false;
1180 
1181  if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1182  $this->save_dump_mail_in_err('Mail with topic '.$this->subject);
1183  }
1184  } else {
1185  dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
1186  }
1187  } else {
1188  // Send mail method not correctly defined
1189  // --------------------------------------
1190 
1191  return 'Bad value for sendmode';
1192  }
1193 
1194  // Now we delete image files that were created dynamically to manage data inline files
1195  foreach ($this->html_images as $val) {
1196  if (!empty($val['type']) && $val['type'] == 'cidfromdata') {
1197  //dol_delete($val['fullpath']);
1198  }
1199  }
1200 
1201  $parameters = array('sent' => $res);
1202  $action = '';
1203  $reshook = $hookmanager->executeHooks('sendMailAfter', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1204  if ($reshook < 0) {
1205  $this->error = "Error in hook maildao sendMailAfter ".$reshook;
1206  dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
1207 
1208  return $reshook;
1209  }
1210  } else {
1211  $this->error = 'No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS';
1212  dol_syslog("CMailFile::sendfile: ".$this->error, LOG_WARNING);
1213  }
1214 
1215  error_reporting($errorlevel); // Reactive niveau erreur origine
1216 
1217  return $res;
1218  }
1219 
1226  public static function encodetorfc2822($stringtoencode)
1227  {
1228  global $conf;
1229  return '=?'.$conf->file->character_set_client.'?B?'.base64_encode($stringtoencode).'?=';
1230  }
1231 
1232  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1239  private function _encode_file($sourcefile)
1240  {
1241  // phpcs:enable
1242  $newsourcefile = dol_osencode($sourcefile);
1243 
1244  if (is_readable($newsourcefile)) {
1245  $contents = file_get_contents($newsourcefile); // Need PHP 4.3
1246  $encoded = chunk_split(base64_encode($contents), 76, $this->eol); // 76 max is defined into http://tools.ietf.org/html/rfc2047
1247  return $encoded;
1248  } else {
1249  $this->error = "Error in _encode_file() method: Can't read file '".$sourcefile."'";
1250  dol_syslog("CMailFile::_encode_file: ".$this->error, LOG_ERR);
1251  return -1;
1252  }
1253  }
1254 
1255 
1256  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1264  public function dump_mail()
1265  {
1266  // phpcs:enable
1267  global $conf, $dolibarr_main_data_root;
1268 
1269  if (@is_writeable($dolibarr_main_data_root)) { // Avoid fatal error on fopen with open_basedir
1270  $outputfile = $dolibarr_main_data_root."/dolibarr_mail.log";
1271  $fp = fopen($outputfile, "w"); // overwrite
1272 
1273  if ($this->sendmode == 'mail') {
1274  fputs($fp, $this->headers);
1275  fputs($fp, $this->eol); // This eol is added by the mail function, so we add it in log
1276  fputs($fp, $this->message);
1277  } elseif ($this->sendmode == 'smtps') {
1278  fputs($fp, $this->smtps->log); // this->smtps->log is filled only if MAIN_MAIL_DEBUG was set to on
1279  } elseif ($this->sendmode == 'swiftmailer') {
1280  fputs($fp, $this->logger->dump()); // this->logger is filled only if MAIN_MAIL_DEBUG was set to on
1281  }
1282 
1283  fclose($fp);
1284  dolChmod($outputfile);
1285  }
1286  }
1287 
1288  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1296  public function save_dump_mail_in_err($message = '')
1297  {
1298  global $dolibarr_main_data_root;
1299 
1300  if (@is_writeable($dolibarr_main_data_root)) { // Avoid fatal error on fopen with open_basedir
1301  $srcfile = $dolibarr_main_data_root."/dolibarr_mail.log";
1302 
1303  // Add message to dolibarr_mail.log. We do not use dol_syslog() on purpose,
1304  // to be sure to write into dolibarr_mail.log
1305  if ($message) {
1306  // Test constant SYSLOG_FILE_NO_ERROR (should stay a constant defined with define('SYSLOG_FILE_NO_ERROR',1);
1307  if (defined('SYSLOG_FILE_NO_ERROR')) {
1308  $filefd = @fopen($srcfile, 'a+');
1309  } else {
1310  $filefd = fopen($srcfile, 'a+');
1311  }
1312  if ($filefd) {
1313  fwrite($filefd, $message."\n");
1314  fclose($filefd);
1315  dolChmod($srcfile);
1316  }
1317  }
1318 
1319  // Move dolibarr_mail.log into a dolibarr_mail.err or dolibarr_mail.date.err
1320  if (getDolGlobalString('MAIN_MAIL_DEBUG_ERR_WITH_DATE')) {
1321  $destfile = $dolibarr_main_data_root."/dolibarr_mail.".dol_print_date(dol_now(), 'dayhourlog', 'gmt').".err";
1322  } else {
1323  $destfile = $dolibarr_main_data_root."/dolibarr_mail.err";
1324  }
1325 
1326  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1327  dol_move($srcfile, $destfile, 0, 1, 0, 0);
1328  }
1329  }
1330 
1331 
1338  public function checkIfHTML($msg)
1339  {
1340  if (!preg_match('/^[\s\t]*<html/i', $msg)) {
1341  $out = "<html><head><title></title>";
1342  if (!empty($this->styleCSS)) {
1343  $out .= $this->styleCSS;
1344  }
1345  $out .= "</head><body";
1346  if (!empty($this->bodyCSS)) {
1347  $out .= $this->bodyCSS;
1348  }
1349  $out .= ">";
1350  $out .= $msg;
1351  $out .= "</body></html>";
1352  } else {
1353  $out = $msg;
1354  }
1355 
1356  return $out;
1357  }
1358 
1364  public function buildCSS()
1365  {
1366  if (!empty($this->css)) {
1367  // Style CSS
1368  $this->styleCSS = '<style type="text/css">';
1369  $this->styleCSS .= 'body {';
1370 
1371  if ($this->css['bgcolor']) {
1372  $this->styleCSS .= ' background-color: '.$this->css['bgcolor'].';';
1373  $this->bodyCSS .= ' bgcolor="'.$this->css['bgcolor'].'"';
1374  }
1375  if ($this->css['bgimage']) {
1376  // TODO recuperer cid
1377  $this->styleCSS .= ' background-image: url("cid:'.$this->css['bgimage_cid'].'");';
1378  }
1379  $this->styleCSS .= '}';
1380  $this->styleCSS .= '</style>';
1381  }
1382  }
1383 
1384 
1385  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1391  public function write_smtpheaders()
1392  {
1393  // phpcs:enable
1394  global $conf;
1395  $out = "";
1396 
1397  $host = dol_getprefix('email');
1398 
1399  // Sender
1400  //$out.= "Sender: ".getValidAddress($this->addr_from,2)).$this->eol2;
1401  $out .= "From: ".$this->getValidAddress($this->addr_from, 3, 1).$this->eol2;
1402  if (!empty($conf->global->MAIN_MAIL_SENDMAIL_FORCE_BA)) {
1403  $out .= "To: ".$this->getValidAddress($this->addr_to, 0, 1).$this->eol2;
1404  }
1405  // 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.
1406  $out .= "Return-Path: ".$this->getValidAddress($this->addr_from, 0, 1).$this->eol2;
1407  if (isset($this->reply_to) && $this->reply_to) {
1408  $out .= "Reply-To: ".$this->getValidAddress($this->reply_to, 2).$this->eol2;
1409  }
1410  if (isset($this->errors_to) && $this->errors_to) {
1411  $out .= "Errors-To: ".$this->getValidAddress($this->errors_to, 2).$this->eol2;
1412  }
1413 
1414  // Receiver
1415  if (isset($this->addr_cc) && $this->addr_cc) {
1416  $out .= "Cc: ".$this->getValidAddress($this->addr_cc, 2).$this->eol2;
1417  }
1418  if (isset($this->addr_bcc) && $this->addr_bcc) {
1419  $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 ?
1420  }
1421 
1422  // Delivery receipt
1423  if (isset($this->deliveryreceipt) && $this->deliveryreceipt == 1) {
1424  $out .= "Disposition-Notification-To: ".$this->getValidAddress($this->addr_from, 2).$this->eol2;
1425  }
1426 
1427  //$out.= "X-Priority: 3".$this->eol2;
1428 
1429  $out .= 'Date: '.date("r").$this->eol2;
1430 
1431  $trackid = $this->trackid;
1432  if ($trackid) {
1433  // References is kept in response and Message-ID is returned into In-Reply-To:
1434  $this->msgid = time().'.phpmail-dolibarr-'.$trackid.'@'.$host;
1435  $out .= 'Message-ID: <'.$this->msgid.">".$this->eol2; // Uppercase seems replaced by phpmail
1436  $out .= 'References: <'.$this->msgid.">".$this->eol2;
1437  $out .= 'X-Dolibarr-TRACKID: '.$trackid.'@'.$host.$this->eol2;
1438  } else {
1439  $this->msgid = time().'.phpmail@'.$host;
1440  $out .= 'Message-ID: <'.$this->msgid.">".$this->eol2;
1441  }
1442 
1443  if (!empty($_SERVER['REMOTE_ADDR'])) {
1444  $out .= "X-RemoteAddr: ".$_SERVER['REMOTE_ADDR'].$this->eol2;
1445  }
1446  $out .= "X-Mailer: Dolibarr version ".DOL_VERSION." (using php mail)".$this->eol2;
1447  $out .= "Mime-Version: 1.0".$this->eol2;
1448 
1449  //$out.= "From: ".$this->getValidAddress($this->addr_from,3,1).$this->eol;
1450 
1451  $out .= "Content-Type: multipart/mixed;".$this->eol2." boundary=\"".$this->mixed_boundary."\"".$this->eol2;
1452  $out .= "Content-Transfer-Encoding: 8bit".$this->eol2; // TODO Seems to be ignored. Header is 7bit once received.
1453 
1454  dol_syslog("CMailFile::write_smtpheaders smtp_header=\n".$out);
1455  return $out;
1456  }
1457 
1458 
1459  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1467  public function write_mimeheaders($filename_list, $mimefilename_list)
1468  {
1469  // phpcs:enable
1470  $mimedone = 0;
1471  $out = "";
1472 
1473  if (is_array($filename_list)) {
1474  $filename_list_size = count($filename_list);
1475  for ($i = 0; $i < $filename_list_size; $i++) {
1476  if ($filename_list[$i]) {
1477  if ($mimefilename_list[$i]) {
1478  $filename_list[$i] = $mimefilename_list[$i];
1479  }
1480  $out .= "X-attachments: $filename_list[$i]".$this->eol2;
1481  }
1482  }
1483  }
1484 
1485  dol_syslog("CMailFile::write_mimeheaders mime_header=\n".$out, LOG_DEBUG);
1486  return $out;
1487  }
1488 
1489  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1496  public function write_body($msgtext)
1497  {
1498  // phpcs:enable
1499  global $conf;
1500 
1501  $out = '';
1502 
1503  $out .= "--".$this->mixed_boundary.$this->eol;
1504 
1505  if ($this->atleastoneimage) {
1506  $out .= "Content-Type: multipart/alternative;".$this->eol." boundary=\"".$this->alternative_boundary."\"".$this->eol;
1507  $out .= $this->eol;
1508  $out .= "--".$this->alternative_boundary.$this->eol;
1509  }
1510 
1511  // Make RFC821 Compliant, replace bare linefeeds
1512  $strContent = preg_replace("/(?<!\r)\n/si", "\r\n", $msgtext); // PCRE modifier /s means new lines are common chars
1513  if (!empty($conf->global->MAIN_FIX_FOR_BUGGED_MTA)) {
1514  $strContent = preg_replace("/\r\n/si", "\n", $strContent); // PCRE modifier /s means new lines are common chars
1515  }
1516 
1517  $strContentAltText = '';
1518  if ($this->msgishtml) {
1519  // Similar code to forge a text from html is also in smtps.class.php
1520  $strContentAltText = preg_replace("/<br\s*[^>]*>/", " ", $strContent);
1521  // TODO We could replace <img ...> with [Filename.ext] like Gmail do.
1522  $strContentAltText = html_entity_decode(strip_tags($strContentAltText)); // Remove any HTML tags
1523  $strContentAltText = trim(wordwrap($strContentAltText, 75, empty($conf->global->MAIN_FIX_FOR_BUGGED_MTA) ? "\r\n" : "\n"));
1524 
1525  // Check if html header already in message, if not complete the message
1526  $strContent = $this->checkIfHTML($strContent);
1527  }
1528 
1529  // Make RFC2045 Compliant, split lines
1530  //$strContent = rtrim(chunk_split($strContent)); // Function chunck_split seems ko if not used on a base64 content
1531  // TODO Encode main content into base64 and use the chunk_split, or quoted-printable
1532  $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.
1533 
1534  if ($this->msgishtml) {
1535  if ($this->atleastoneimage) {
1536  $out .= "Content-Type: text/plain; charset=".$conf->file->character_set_client.$this->eol;
1537  //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1538  $out .= $this->eol.($strContentAltText ? $strContentAltText : strip_tags($strContent)).$this->eol; // Add plain text message
1539  $out .= "--".$this->alternative_boundary.$this->eol;
1540  $out .= "Content-Type: multipart/related;".$this->eol." boundary=\"".$this->related_boundary."\"".$this->eol;
1541  $out .= $this->eol;
1542  $out .= "--".$this->related_boundary.$this->eol;
1543  }
1544 
1545  if (!$this->atleastoneimage && $strContentAltText && !empty($conf->global->MAIN_MAIL_USE_MULTI_PART)) { // Add plain text message part before html part
1546  $out .= "Content-Type: multipart/alternative;".$this->eol." boundary=\"".$this->alternative_boundary."\"".$this->eol;
1547  $out .= $this->eol;
1548  $out .= "--".$this->alternative_boundary.$this->eol;
1549  $out .= "Content-Type: text/plain; charset=".$conf->file->character_set_client.$this->eol;
1550  //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1551  $out .= $this->eol.$strContentAltText.$this->eol;
1552  $out .= "--".$this->alternative_boundary.$this->eol;
1553  }
1554 
1555  $out .= "Content-Type: text/html; charset=".$conf->file->character_set_client.$this->eol;
1556  //$out.= "Content-Transfer-Encoding: 7bit".$this->eol; // TODO Use base64
1557  $out .= $this->eol.$strContent.$this->eol;
1558 
1559  if (!$this->atleastoneimage && $strContentAltText && !empty($conf->global->MAIN_MAIL_USE_MULTI_PART)) { // Add plain text message part after html part
1560  $out .= "--".$this->alternative_boundary."--".$this->eol;
1561  }
1562  } else {
1563  $out .= "Content-Type: text/plain; charset=".$conf->file->character_set_client.$this->eol;
1564  //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1565  $out .= $this->eol.$strContent.$this->eol;
1566  }
1567 
1568  $out .= $this->eol;
1569 
1570  // Encode images
1571  if ($this->atleastoneimage) {
1572  $out .= $this->write_images($this->images_encoded);
1573  // always end related and end alternative after inline images
1574  $out .= "--".$this->related_boundary."--".$this->eol;
1575  $out .= $this->eol."--".$this->alternative_boundary."--".$this->eol;
1576  $out .= $this->eol;
1577  }
1578 
1579  return $out;
1580  }
1581 
1582  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1592  private function write_files($filename_list, $mimetype_list, $mimefilename_list, $cidlist)
1593  {
1594  // phpcs:enable
1595  $out = '';
1596 
1597  $filename_list_size = count($filename_list);
1598  for ($i = 0; $i < $filename_list_size; $i++) {
1599  if ($filename_list[$i]) {
1600  dol_syslog("CMailFile::write_files: i=$i");
1601  $encoded = $this->_encode_file($filename_list[$i]);
1602  if ($encoded >= 0) {
1603  if ($mimefilename_list[$i]) {
1604  $filename_list[$i] = $mimefilename_list[$i];
1605  }
1606  if (!$mimetype_list[$i]) {
1607  $mimetype_list[$i] = "application/octet-stream";
1608  }
1609 
1610  $out .= "--".$this->mixed_boundary.$this->eol;
1611  $out .= "Content-Disposition: attachment; filename=\"".$filename_list[$i]."\"".$this->eol;
1612  $out .= "Content-Type: ".$mimetype_list[$i]."; name=\"".$filename_list[$i]."\"".$this->eol;
1613  $out .= "Content-Transfer-Encoding: base64".$this->eol;
1614  $out .= "Content-Description: ".$filename_list[$i].$this->eol;
1615  if (!empty($cidlist) && is_array($cidlist) && $cidlist[$i]) {
1616  $out .= "X-Attachment-Id: ".$cidlist[$i].$this->eol;
1617  $out .= "Content-ID: <".$cidlist[$i].'>'.$this->eol;
1618  }
1619  $out .= $this->eol;
1620  $out .= $encoded;
1621  $out .= $this->eol;
1622  //$out.= $this->eol;
1623  } else {
1624  return $encoded;
1625  }
1626  }
1627  }
1628 
1629  return $out;
1630  }
1631 
1632 
1633  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1640  public function write_images($images_list)
1641  {
1642  // phpcs:enable
1643  $out = '';
1644 
1645  if (is_array($images_list)) {
1646  foreach ($images_list as $img) {
1647  dol_syslog("CMailFile::write_images: ".$img["name"]);
1648 
1649  $out .= "--".$this->related_boundary.$this->eol; // always related for an inline image
1650  $out .= "Content-Type: ".$img["content_type"]."; name=\"".$img["name"]."\"".$this->eol;
1651  $out .= "Content-Transfer-Encoding: base64".$this->eol;
1652  $out .= "Content-Disposition: inline; filename=\"".$img["name"]."\"".$this->eol;
1653  $out .= "Content-ID: <".$img["cid"].">".$this->eol;
1654  $out .= $this->eol;
1655  $out .= $img["image_encoded"];
1656  $out .= $this->eol;
1657  }
1658  }
1659 
1660  return $out;
1661  }
1662 
1663 
1664  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1672  public function check_server_port($host, $port)
1673  {
1674  // phpcs:enable
1675  global $conf;
1676 
1677  $_retVal = 0;
1678  $timeout = 5; // Timeout in seconds
1679 
1680  if (function_exists('fsockopen')) {
1681  $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER';
1682  $keyforsmtpport = 'MAIN_MAIL_SMTP_PORT';
1683  $keyforsmtpid = 'MAIN_MAIL_SMTPS_ID';
1684  $keyforsmtppw = 'MAIN_MAIL_SMTPS_PW';
1685  $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE';
1686  $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE';
1687  $keyfortls = 'MAIN_MAIL_EMAIL_TLS';
1688  $keyforstarttls = 'MAIN_MAIL_EMAIL_STARTTLS';
1689  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
1690 
1691  if (!empty($this->sendcontext)) {
1692  $smtpContextKey = strtoupper($this->sendcontext);
1693  $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_'.$smtpContextKey);
1694  if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
1695  $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER_'.$smtpContextKey;
1696  $keyforsmtpport = 'MAIN_MAIL_SMTP_PORT_'.$smtpContextKey;
1697  $keyforsmtpid = 'MAIN_MAIL_SMTPS_ID_'.$smtpContextKey;
1698  $keyforsmtppw = 'MAIN_MAIL_SMTPS_PW_'.$smtpContextKey;
1699  $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE_'.$smtpContextKey;
1700  $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE_'.$smtpContextKey;
1701  $keyfortls = 'MAIN_MAIL_EMAIL_TLS_'.$smtpContextKey;
1702  $keyforstarttls = 'MAIN_MAIL_EMAIL_STARTTLS_'.$smtpContextKey;
1703  $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_'.$smtpContextKey;
1704  }
1705  }
1706 
1707  // If we use SSL/TLS
1708  if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
1709  $host = 'ssl://'.$host;
1710  }
1711  // tls smtp start with no encryption
1712  //if (!empty($conf->global->MAIN_MAIL_EMAIL_STARTTLS) && function_exists('openssl_open')) $host='tls://'.$host;
1713 
1714  dol_syslog("Try socket connection to host=".$host." port=".$port);
1715  //See if we can connect to the SMTP server
1716  $errno = 0; $errstr = '';
1717  if ($socket = @fsockopen(
1718  $host, // Host to test, IP or domain. Add ssl:// for SSL/TLS.
1719  $port, // which Port number to use
1720  $errno, // actual system level error
1721  $errstr, // and any text that goes with the error
1722  $timeout // timeout for reading/writing data over the socket
1723  )) {
1724  // Windows still does not have support for this timeout function
1725  if (function_exists('stream_set_timeout')) {
1726  stream_set_timeout($socket, $timeout, 0);
1727  }
1728 
1729  dol_syslog("Now we wait for answer 220");
1730 
1731  // Check response from Server
1732  if ($_retVal = $this->server_parse($socket, "220")) {
1733  $_retVal = $socket;
1734  }
1735  } else {
1736  $this->error = utf8_check('Error '.$errno.' - '.$errstr) ? 'Error '.$errno.' - '.$errstr : utf8_encode('Error '.$errno.' - '.$errstr);
1737  }
1738  }
1739  return $_retVal;
1740  }
1741 
1742  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1751  public function server_parse($socket, $response)
1752  {
1753  // phpcs:enable
1754  $_retVal = true; // Indicates if Object was created or not
1755  $server_response = '';
1756 
1757  while (substr($server_response, 3, 1) != ' ') {
1758  if (!($server_response = fgets($socket, 256))) {
1759  $this->error = "Couldn't get mail server response codes";
1760  return false;
1761  }
1762  }
1763 
1764  if (!(substr($server_response, 0, 3) == $response)) {
1765  $this->error = "Ran into problems sending Mail.\r\nResponse: $server_response";
1766  $_retVal = false;
1767  }
1768 
1769  return $_retVal;
1770  }
1771 
1778  private function findHtmlImages($images_dir)
1779  {
1780  // Build the array of image extensions
1781  $extensions = array_keys($this->image_types);
1782 
1783  // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
1784  // For example when:
1785  // <img alt="" src="/viewimage.php?modulepart=medias&amp;entity=1&amp;file=image/picture.jpg" style="height:356px; width:1040px" />
1786  $matches = array();
1787  preg_match_all('/(?:"|\')([^"\']+\.('.implode('|', $extensions).'))(?:"|\')/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
1788 
1789  if (!empty($matches)) {
1790  $i = 0;
1791  // We are interested in $matches[1] only (the second set of parenthesis into regex)
1792  foreach ($matches[1] as $full) {
1793  $regs = array();
1794  if (preg_match('/file=([A-Za-z0-9_\-\/]+[\.]?[A-Za-z0-9]+)?$/i', $full, $regs)) { // If xxx is 'file=aaa'
1795  $img = $regs[1];
1796 
1797  if (file_exists($images_dir.'/'.$img)) {
1798  // Image path in src
1799  $src = preg_quote($full, '/');
1800  // Image full path
1801  $this->html_images[$i]["fullpath"] = $images_dir.'/'.$img;
1802  // Image name
1803  $this->html_images[$i]["name"] = $img;
1804  // Content type
1805  $regext = array();
1806  if (preg_match('/^.+\.(\w{3,4})$/', $img, $regext)) {
1807  $ext = strtolower($regext[1]);
1808  $this->html_images[$i]["content_type"] = $this->image_types[$ext];
1809  }
1810  // cid
1811  $this->html_images[$i]["cid"] = dol_hash($this->html_images[$i]["fullpath"], 'md5'); // Force md5 hash (does not contain special chars)
1812  // type
1813  $this->html_images[$i]["type"] = 'cidfromurl';
1814 
1815  $this->html = preg_replace("/src=\"$src\"|src='$src'/i", "src=\"cid:".$this->html_images[$i]["cid"]."\"", $this->html);
1816  }
1817  $i++;
1818  }
1819  }
1820 
1821  if (!empty($this->html_images)) {
1822  $inline = array();
1823 
1824  $i = 0;
1825 
1826  foreach ($this->html_images as $img) {
1827  $fullpath = $images_dir.'/'.$img["name"];
1828 
1829  // If duplicate images are embedded, they may show up as attachments, so remove them.
1830  if (!in_array($fullpath, $inline)) {
1831  // Read image file
1832  if ($image = file_get_contents($fullpath)) {
1833  // On garde que le nom de l'image
1834  $regs = array();
1835  preg_match('/([A-Za-z0-9_-]+[\.]?[A-Za-z0-9]+)?$/i', $img["name"], $regs);
1836  $imgName = $regs[1];
1837 
1838  $this->images_encoded[$i]['name'] = $imgName;
1839  $this->images_encoded[$i]['fullpath'] = $fullpath;
1840  $this->images_encoded[$i]['content_type'] = $img["content_type"];
1841  $this->images_encoded[$i]['cid'] = $img["cid"];
1842  // Encodage de l'image
1843  $this->images_encoded[$i]["image_encoded"] = chunk_split(base64_encode($image), 68, $this->eol);
1844  $inline[] = $fullpath;
1845  }
1846  }
1847  $i++;
1848  }
1849  } else {
1850  return -1;
1851  }
1852 
1853  return 1;
1854  } else {
1855  return 0;
1856  }
1857  }
1858 
1866  private function findHtmlImagesIsSrcData($images_dir)
1867  {
1868  global $conf;
1869 
1870  // Build the array of image extensions
1871  $extensions = array_keys($this->image_types);
1872 
1873  if ($images_dir && !dol_is_dir($images_dir)) {
1874  dol_mkdir($images_dir, DOL_DATA_ROOT);
1875  }
1876 
1877  // Uncomment this for debug
1878  /*
1879  global $dolibarr_main_data_root;
1880  $outputfile = $dolibarr_main_data_root."/dolibarr_mail.log";
1881  $fp = fopen($outputfile, "w+");
1882  fwrite($fp, $this->html);
1883  fclose($fp);
1884  */
1885 
1886  // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
1887  // For example when:
1888  // <img alt="" src="/viewimage.php?modulepart=medias&amp;entity=1&amp;file=image/picture.jpg" style="height:356px; width:1040px" />
1889  $matches = array();
1890  preg_match_all('/src="data:image\/('.implode('|', $extensions).');base64,([^"]+)"/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
1891 
1892  if (!empty($matches) && !empty($matches[1])) {
1893  if (empty($images_dir)) {
1894  // No temp directory provided, so we are not able to support convertion of data:image into physical images.
1895  $this->error = 'NoTempDirProvidedInCMailConstructorSoCantConvertDataImgOnDisk';
1896  return -1;
1897  }
1898 
1899  $i = 0;
1900  foreach ($matches[1] as $key => $ext) {
1901  // We save the image to send in disk
1902  $filecontent = $matches[2][$key];
1903 
1904  $cid = 'cid000'.dol_hash($filecontent, 'md5'); // The id must not change if image is same
1905 
1906  $destfiletmp = $images_dir.'/'.$cid.'.'.$ext;
1907 
1908  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)
1909  dol_syslog("write the cid file ".$destfiletmp);
1910  $fhandle = @fopen($destfiletmp, 'w');
1911  if ($fhandle) {
1912  $nbofbyteswrote = fwrite($fhandle, base64_decode($filecontent));
1913  fclose($fhandle);
1914  dolChmod($destfiletmp);
1915  } else {
1916  $this->errors[] = "Failed to open file '".$destfiletmp."' for write";
1917  return -1;
1918  }
1919  }
1920 
1921  if (file_exists($destfiletmp)) {
1922  // Image full path
1923  $this->html_images[$i]["fullpath"] = $destfiletmp;
1924  // Image name
1925  $this->html_images[$i]["name"] = basename($destfiletmp);
1926  // Content type
1927  $this->html_images[$i]["content_type"] = $this->image_types[strtolower($ext)];
1928  // cid
1929  $this->html_images[$i]["cid"] = $cid;
1930  // type
1931  $this->html_images[$i]["type"] = 'cidfromdata';
1932 
1933  $this->html = str_replace('src="data:image/'.$ext.';base64,'.$filecontent.'"', 'src="cid:'.$this->html_images[$i]["cid"].'"', $this->html);
1934  }
1935  $i++;
1936  }
1937 
1938  return 1;
1939  } else {
1940  return 0;
1941  }
1942  }
1943 
1959  public static function getValidAddress($address, $format, $encode = 0, $maxnumberofemail = 0)
1960  {
1961  global $conf;
1962 
1963  $ret = '';
1964 
1965  $arrayaddress = explode(',', $address);
1966 
1967  // Boucle sur chaque composant de l'adresse
1968  $i = 0;
1969  foreach ($arrayaddress as $val) {
1970  $regs = array();
1971  if (preg_match('/^(.*)<(.*)>$/i', trim($val), $regs)) {
1972  $name = trim($regs[1]);
1973  $email = trim($regs[2]);
1974  } else {
1975  $name = '';
1976  $email = trim($val);
1977  }
1978 
1979  if ($email) {
1980  $i++;
1981 
1982  $newemail = '';
1983  if ($format == 5) {
1984  $newemail = $name ? $name : $email;
1985  $newemail = '<a href="mailto:'.$email.'">'.$newemail.'</a>';
1986  }
1987  if ($format == 4) {
1988  $newemail = $name ? $name : $email;
1989  }
1990  if ($format == 2) {
1991  $newemail = $email;
1992  }
1993  if ($format == 1 || $format == 3) {
1994  $newemail = '<'.$email.'>';
1995  }
1996  if ($format == 0 || $format == 3) {
1997  if (!empty($conf->global->MAIN_MAIL_NO_FULL_EMAIL)) {
1998  $newemail = '<'.$email.'>';
1999  } elseif (!$name) {
2000  $newemail = '<'.$email.'>';
2001  } else {
2002  $newemail = ($format == 3 ? '"' : '').($encode ?self::encodetorfc2822($name) : $name).($format == 3 ? '"' : '').' <'.$email.'>';
2003  }
2004  }
2005 
2006  $ret = ($ret ? $ret.',' : '').$newemail;
2007 
2008  // Stop if we have too much records
2009  if ($maxnumberofemail && $i >= $maxnumberofemail) {
2010  if (count($arrayaddress) > $maxnumberofemail) {
2011  $ret .= '...';
2012  }
2013  break;
2014  }
2015  }
2016  }
2017 
2018  return $ret;
2019  }
2020 
2028  public static function getArrayAddress($address)
2029  {
2030  global $conf;
2031 
2032  $ret = array();
2033 
2034  $arrayaddress = explode(',', $address);
2035 
2036  // Boucle sur chaque composant de l'adresse
2037  foreach ($arrayaddress as $val) {
2038  if (preg_match('/^(.*)<(.*)>$/i', trim($val), $regs)) {
2039  $name = trim($regs[1]);
2040  $email = trim($regs[2]);
2041  } else {
2042  $name = null;
2043  $email = trim($val);
2044  }
2045 
2046  $ret[$email] = empty($conf->global->MAIN_MAIL_NO_FULL_EMAIL) ? $name : null;
2047  }
2048 
2049  return $ret;
2050  }
2051 }
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
_encode_file($sourcefile)
Read a file on disk and return encoded content for emails (mode = 'mail')
write_body($msgtext)
Return email content (mode = 'mail')
$bodyCSS
Defined background directly in body tag.
dump_mail()
Write content of a SMTP request into a dump file (mode = all) Used for debugging.
sendfile()
Send mail that was prepared by constructor.
save_dump_mail_in_err($message='')
Save content if mail is in error Used for debugging.
static encodetorfc2822($stringtoencode)
Encode subject according to RFC 2822 - http://en.wikipedia.org/wiki/MIME#Encoded-Word.
checkIfHTML($msg)
Correct an uncomplete html string.
static getValidAddress($address, $format, $encode=0, $maxnumberofemail=0)
Return a formatted address string for SMTP protocol.
write_images($images_list)
Attach an image to email (mode = 'mail')
server_parse($socket, $response)
This function has been modified as provided by SirSir to allow multiline responses when using SMTP Ex...
__construct($subject, $to, $from, $msg, $filename_list=array(), $mimetype_list=array(), $mimefilename_list=array(), $addr_cc="", $addr_bcc="", $deliveryreceipt=0, $msgishtml=0, $errors_to='', $css='', $trackid='', $moreinheader='', $sendcontext='standard', $replyto='', $upload_dir_tmp='')
CMailFile.
write_smtpheaders()
Create SMTP headers (mode = 'mail')
findHtmlImagesIsSrcData($images_dir)
Seearch images with data:image format into html message.
$styleCSS
Defined css style for body background.
write_mimeheaders($filename_list, $mimefilename_list)
Create header MIME (mode = 'mail')
check_server_port($host, $port)
Try to create a socket connection.
buildCSS()
Build a css style (mode = all) into this->styleCSS and this->bodyCSS.
write_files($filename_list, $mimetype_list, $mimefilename_list, $cidlist)
Attach file to email (mode = 'mail')
static getArrayAddress($address)
Return a formatted array of address string for SMTP protocol.
findHtmlImages($images_dir)
Search images into html message and init array this->images_encoded if found.
Class to manage hooks.
Class to construct and send SMTP compliant email, even to a secure SMTP server, regardless of platfor...
Definition: smtps.class.php:47
dol_is_file($pathoffile)
Return if path is a file.
Definition: files.lib.php:483
dol_is_dir($folder)
Test if filename is a directory.
Definition: files.lib.php:453
dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1, $testvirus=0, $indexdatabase=1)
Move a file into another name.
Definition: files.lib.php:947
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
dol_nl2br($stringtoencode, $nl2brmode=0, $forxml=false)
Replace CRLF in string with a HTML BR tag.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dolChmod($filepath, $newmask='')
Change mod of a file.
dol_now($mode='auto')
Return date for now.
dol_string_unaccent($str)
Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName.
ascii_check($str)
Check if a string is in ASCII.
dol_textishtml($msg, $option=0)
Return if a text is a html content.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
utf8_check($str)
Check if a string is in UTF8.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
dol_sanitizeEmail($stringtoclean)
Clean a string to use it as an Email.
dol_hash($chain, $type='0')
Returns a hash (non reversible encryption) of a string.