diff --git a/htdocs/admin/mails.php b/htdocs/admin/mails.php
index 1a25af49789..dfa0bccb06b 100644
--- a/htdocs/admin/mails.php
+++ b/htdocs/admin/mails.php
@@ -34,14 +34,15 @@ $langs->loadLangs(array("companies", "products", "admin", "mails", "other", "err
$action = GETPOST('action', 'aZ09');
$cancel = GETPOST('cancel', 'aZ09');
+$trackid = GETPOST('trackid');
+
if (!$user->admin) {
accessforbidden();
}
$usersignature = $user->signature;
-// For action = test or send, we ensure that content is not html, even for signature, because this we want a test with NO html.
-
-if ($action == 'test' || $action == 'send') {
+// For action = test or send, we ensure that content is not html, even for signature, because for this we want a test with NO html.
+if ($action == 'test' || ($action == 'send' && $trackid = 'test')) {
$usersignature = dol_string_nohtmltag($usersignature, 2);
}
@@ -130,7 +131,7 @@ $actiontypecode = ''; // Not an event for agenda
$triggersendname = ''; // Disable triggers
$paramname = 'id';
$mode = 'emailfortest';
-$trackid = (($action == 'testhtml') ? "testhtml" : "test");
+$trackid = ($action == 'send' ? GETPOST('trackid', 'aZ09') : $action);
$sendcontext = '';
include DOL_DOCUMENT_ROOT.'/core/actions_sendmails.inc.php';
@@ -143,7 +144,6 @@ if ($action == 'presend' && GETPOST('trackid', 'alphanohtml') == 'testhtml') {
-
/*
* View
*/
diff --git a/htdocs/core/actions_sendmails.inc.php b/htdocs/core/actions_sendmails.inc.php
index 3749b403469..772d2b7ed5d 100644
--- a/htdocs/core/actions_sendmails.inc.php
+++ b/htdocs/core/actions_sendmails.inc.php
@@ -108,6 +108,10 @@ if (($action == 'send' || $action == 'relance') && !GETPOST('addfile') && !GETPO
$trackid = GETPOST('trackid', 'aZ09');
}
+ // Set tmp user directory (used to convert images embedded as img src=data:image)
+ $vardir = $conf->user->dir_output."/".$user->id;
+ $upload_dir_tmp = $vardir.'/temp'; // TODO Add $keytoavoidconflict in upload_dir path
+
$subject = '';
//$actionmsg = '';
$actionmsg2 = '';
@@ -359,7 +363,7 @@ if (($action == 'send' || $action == 'relance') && !GETPOST('addfile') && !GETPO
if (empty($sendcontext)) {
$sendcontext = 'standard';
}
- $mailfile = new CMailFile($subject, $sendto, $from, $message, $filepath, $mimetype, $filename, $sendtocc, $sendtobcc, $deliveryreceipt, -1, '', '', $trackid, '', $sendcontext);
+ $mailfile = new CMailFile($subject, $sendto, $from, $message, $filepath, $mimetype, $filename, $sendtocc, $sendtobcc, $deliveryreceipt, -1, '', '', $trackid, '', $sendcontext, '', $upload_dir_tmp);
if (!empty($mailfile->error) || !empty($mailfile->errors)) {
setEventMessages($mailfile->error, $mailfile->errors, 'errors');
diff --git a/htdocs/core/class/CMailFile.class.php b/htdocs/core/class/CMailFile.class.php
index b3ff0aae930..ffd1d6e8e5d 100644
--- a/htdocs/core/class/CMailFile.class.php
+++ b/htdocs/core/class/CMailFile.class.php
@@ -120,6 +120,10 @@ class CMailFile
* @var array filenames list (List of attached file name in message)
*/
public $mimefilename_list = array();
+ /**
+ * @var array filenames cid
+ */
+ public $cid_list = array();
// Image
public $html;
@@ -159,8 +163,9 @@ class CMailFile
* @param string $moreinheader More in header. $moreinheader must contains the "\r\n" (TODO not supported for other MAIL_SEND_MODE different than 'mail' and 'smtps' for the moment)
* @param string $sendcontext 'standard', 'emailing', ... (used to define which sending mode and parameters to use)
* @param string $replyto Reply-to email (will be set to same value than From by default if not provided)
+ * @param string $upload_dir_tmp Temporary directory (used to convert images embedded as img src=data:image)
*/
- 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 = '')
+ 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 = '')
{
global $conf, $dolibarr_main_data_root, $user;
@@ -171,6 +176,8 @@ class CMailFile
}
}
+ $cid_list = array();
+
$this->sendcontext = $sendcontext;
// Define this->sendmode ('mail', 'smtps', 'siwftmailer', ...) according to $sendcontext ('standard', 'emailing', 'ticket')
@@ -248,26 +255,41 @@ class CMailFile
$findimg = 0;
if (!empty($conf->global->MAIN_MAIL_ADD_INLINE_IMAGES_IF_IN_MEDIAS)) {
+ // Search into the body for html_images[$i]['name'], LOG_DEBUG);
}
}
}
}
- // Define if there is at least one file
+ // Set atleastoneimage if there is at least one file (into $filename_list array)
if (is_array($filename_list)) {
foreach ($filename_list as $i => $val) {
if ($filename_list[$i]) {
$this->atleastonefile = 1;
- dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i], LOG_DEBUG);
+ 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);
}
}
}
@@ -303,9 +325,6 @@ class CMailFile
$this->addr_to = $to;
$this->addr_from = $from;
$this->msg = $msg;
- $this->filename_list = $filename_list;
- $this->mimetype_list = $mimetype_list;
- $this->mimefilename_list = $mimefilename_list;
$this->addr_cc = $addr_cc;
$this->addr_bcc = $addr_bcc;
$this->deliveryreceipt = $deliveryreceipt;
@@ -315,9 +334,11 @@ class CMailFile
$this->reply_to = $replyto;
$this->errors_to = $errors_to;
$this->trackid = $trackid;
+ // Set arrays with attached files info
$this->filename_list = $filename_list;
$this->mimetype_list = $mimetype_list;
$this->mimefilename_list = $mimefilename_list;
+ $this->cid_list = $cid_list;
if (!empty($conf->global->MAIN_MAIL_FORCE_SENDTO)) {
$this->addr_to = $conf->global->MAIN_MAIL_FORCE_SENDTO;
@@ -368,7 +389,7 @@ class CMailFile
// Add attachments to text_encoded
if (!empty($this->atleastonefile)) {
- $files_encoded = $this->write_files($filename_list, $mimetype_list, $mimefilename_list);
+ $files_encoded = $this->write_files($filename_list, $mimetype_list, $mimefilename_list, $cid_list);
}
// We now define $this->headers and $this->message
@@ -432,7 +453,7 @@ class CMailFile
if (!empty($this->atleastonefile)) {
foreach ($filename_list as $i => $val) {
$content = file_get_contents($filename_list[$i]);
- $smtps->setAttachment($content, $mimefilename_list[$i], $mimetype_list[$i]);
+ $smtps->setAttachment($content, $mimefilename_list[$i], $mimetype_list[$i], $cid_list[$i]);
}
}
@@ -487,6 +508,7 @@ class CMailFile
if (!empty($conf->global->MAIN_FORCE_DISABLE_MAIL_SPOOFING)) {
// Prevent email spoofing for smtp server with a strict configuration
$regexp = '/([a-z0-9_\.\-\+])+\@(([a-z0-9\-])+\.)+([a-z0-9]{2,4})+/i'; // This regular expression extracts all emails from a string
+ $adressEmailFrom = array();
$emailMatchs = preg_match_all($regexp, $from, $adressEmailFrom);
$adressEmailFrom = reset($adressEmailFrom);
if ($emailMatchs !== false && filter_var($conf->global->MAIN_MAIL_SMTPS_ID, FILTER_VALIDATE_EMAIL) && $conf->global->MAIN_MAIL_SMTPS_ID !== $adressEmailFrom) {
@@ -1380,7 +1402,8 @@ class CMailFile
if ($this->msgishtml) {
// Similar code to forge a text from html is also in smtps.class.php
$strContentAltText = preg_replace("/
]*>/", " ", $strContent);
- $strContentAltText = html_entity_decode(strip_tags($strContentAltText));
+ // TODO We could replace with [Filename.ext] like Gmail do.
+ $strContentAltText = html_entity_decode(strip_tags($strContentAltText)); // Remove any HTML tags
$strContentAltText = trim(wordwrap($strContentAltText, 75, empty($conf->global->MAIN_FIX_FOR_BUGGED_MTA) ? "\r\n" : "\n"));
// Check if html header already in message, if not complete the message
@@ -1447,9 +1470,10 @@ class CMailFile
* @param array $filename_list Tableau
* @param array $mimetype_list Tableau
* @param array $mimefilename_list Tableau
- * @return string Chaine fichiers encodes
+ * @param array $cidlist Array of CID if file must be completed with CID code
+ * @return string String with files encoded
*/
- public function write_files($filename_list, $mimetype_list, $mimefilename_list)
+ private function write_files($filename_list, $mimetype_list, $mimefilename_list, $cidlist)
{
// phpcs:enable
$out = '';
@@ -1472,6 +1496,10 @@ class CMailFile
$out .= "Content-Type: ".$mimetype_list[$i]."; name=\"".$filename_list[$i]."\"".$this->eol;
$out .= "Content-Transfer-Encoding: base64".$this->eol;
$out .= "Content-Description: ".$filename_list[$i].$this->eol;
+ if (!empty($cidlist) && is_array($cidlist) && $cidlist[$i]) {
+ $out .= "X-Attachment-Id: ".$cidlist[$i].$this->eol;
+ $out .= "Content-ID: <".$cidlist[$i].'>'.$this->eol;
+ }
$out .= $this->eol;
$out .= $encoded;
$out .= $this->eol;
@@ -1628,41 +1656,46 @@ class CMailFile
/**
* Seearch images into html message and init array this->images_encoded if found
*
- * @param string $images_dir Location of physical images files
+ * @param string $images_dir Location of physical images files. For example $dolibarr_main_data_root.'/medias'
* @return int >0 if OK, <0 if KO
*/
- public function findHtmlImages($images_dir)
+ private function findHtmlImages($images_dir)
{
- // Build the list of image extensions
+ // Build the array of image extensions
$extensions = array_keys($this->image_types);
+ // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
+ // For example when:
+ //
$matches = array();
preg_match_all('/(?:"|\')([^"\']+\.('.implode('|', $extensions).'))(?:"|\')/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
if (!empty($matches)) {
$i = 0;
+ // We are interested in $matches[1] only (the second set of parenthesis into regex)
foreach ($matches[1] as $full) {
+ $regs = array();
if (preg_match('/file=([A-Za-z0-9_\-\/]+[\.]?[A-Za-z0-9]+)?$/i', $full, $regs)) { // If xxx is 'file=aaa'
$img = $regs[1];
if (file_exists($images_dir.'/'.$img)) {
// Image path in src
$src = preg_quote($full, '/');
-
// Image full path
$this->html_images[$i]["fullpath"] = $images_dir.'/'.$img;
-
// Image name
$this->html_images[$i]["name"] = $img;
-
// Content type
- if (preg_match('/^.+\.(\w{3,4})$/', $img, $reg)) {
- $ext = strtolower($reg[1]);
+ $regext = array();
+ if (preg_match('/^.+\.(\w{3,4})$/', $img, $regext)) {
+ $ext = strtolower($regext[1]);
$this->html_images[$i]["content_type"] = $this->image_types[$ext];
}
-
// cid
$this->html_images[$i]["cid"] = dol_hash(uniqid(time()), 3); // Force md5 hash (does not contains special chars)
+ // type
+ $this->html_images[$i]["type"] = 'cidfromurl';
+
$this->html = preg_replace("/src=\"$src\"|src='$src'/i", "src=\"cid:".$this->html_images[$i]["cid"]."\"", $this->html);
}
$i++;
@@ -1682,6 +1715,7 @@ class CMailFile
// Read image file
if ($image = file_get_contents($fullpath)) {
// On garde que le nom de l'image
+ $regs = array();
preg_match('/([A-Za-z0-9_-]+[\.]?[A-Za-z0-9]+)?$/i', $img["name"], $regs);
$imgName = $regs[1];
@@ -1706,6 +1740,85 @@ class CMailFile
}
}
+ /**
+ * Seearch images with data:image format into html message
+ *
+ * @param string $images_dir Location of where to store physicaly images files. For example $dolibarr_main_data_root.'/medias'
+ * @return int >0 if OK, <0 if KO
+ */
+ private function findHtmlImagesIsSrcData($images_dir)
+ {
+ global $conf;
+
+ // Build the array of image extensions
+ $extensions = array_keys($this->image_types);
+
+ if ($images_dir && !dol_is_dir($images_dir)) {
+ dol_mkdir($images_dir, DOL_DATA_ROOT);
+ }
+
+ // Uncomment this for debug
+ /*
+ global $dolibarr_main_data_root;
+ $outputfile = $dolibarr_main_data_root."/dolibarr_mail.log";
+ $fp = fopen($outputfile, "w");
+ fwrite($fp, $this->html);
+ fclose($fp);
+ */
+
+ // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
+ // For example when:
+ //
+ $matches = array();
+ preg_match_all('/src="data:image\/('.implode('|', $extensions).');base64,([^"]+)"/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
+
+ if (!empty($matches)) {
+ if (empty($images_dir)) {
+ // No temp directory provided, so we are not able to support convertion of data:image into physical images.
+ $this->error = 'NoTempDirProvidedInCMailConstructorSoCantConvertDataImgOnDisk';
+ return -1;
+ }
+
+ $i = 0;
+ foreach ($matches[1] as $key => $ext) {
+ // We save the image to send in disk
+ $filecontent = $matches[2][$key];
+ $cid = dol_hash(uniqid(time()), 3);
+ $destfiletmp = $images_dir.'/'.$cid.'.'.$ext;
+
+ $fhandle = @fopen($destfiletmp, 'w');
+ if ($fhandle) {
+ $nbofbyteswrote = fwrite($fhandle, base64_decode($filecontent));
+ fclose($fhandle);
+ @chmod($destfiletmp, octdec($conf->global->MAIN_UMASK));
+ } else {
+ $this->errors[] = "Failed to open file '".$destfiletmp."' for write";
+ return -1;
+ }
+
+ if (file_exists($destfiletmp)) {
+ // Image full path
+ $this->html_images[$i]["fullpath"] = $destfiletmp;
+ // Image name
+ $this->html_images[$i]["name"] = basename($destfiletmp);
+ // Content type
+ $this->html_images[$i]["content_type"] = $this->image_types[strtolower($ext)];
+ // cid
+ $this->html_images[$i]["cid"] = $cid;
+ // type
+ $this->html_images[$i]["type"] = 'cidfromdata';
+
+ $this->html = preg_replace('/src="data:image\/'.$ext.';base64,'.preg_quote($filecontent, '/').'"/', 'src="cid:'.$this->html_images[$i]["cid"].'"', $this->html);
+ }
+ $i++;
+ }
+
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
/**
* Return a formatted address string for SMTP protocol
*
diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php
index 16302dc3c1a..41cf2482f9a 100644
--- a/htdocs/core/class/html.formmail.class.php
+++ b/htdocs/core/class/html.formmail.class.php
@@ -1193,7 +1193,7 @@ class FormMail extends Form
$out .= "\n";
} else {
$out = '