Permit to easyly place an HTML part in your e-mail or to add pictures.
<?
/**
A simple class to handle e-mails.
NOTE: The whole web site uses UTF-8 encoded characters.
ClassMail::LOCAL_DEV:
First, consider that there are two working ways : in development or in production. Set its value to true during the development phase :
- for the address of the percipient in the header field to be set to your personnal local e-mail address. The MTA of your local system will capture e-mails, instead of the one of the real host.
- for a informative message to be displayed at the end of the HTML page (see $gLocalDevString),
- for the scripts to die with a meaningful message if a picture file does not exist.
Privacity:
I decided that in case of multiple percipients, the percipients list will be put in a "Bcc:" header field (blind carbon copy) and the "To:" header field to contain a fake e-mail address. The other possibility to preserve the privacity of percipients is to send mails one by one, putting the percipient address in the "To:" field. To change this politic, simply change the constant declaration: "const kMaxMails" to 1. This constant is actualy set to 50. Usualy, the hosters admit much more that 50 percipients per mail, but 50 looks good to me. The list of percipient addresses, partialy represented, is placed in an header field (X-PHP-Comment:) for developpemnt facilities. Search this string "·*·*·" in this source, and remove the corresponding block of code before placing this code in production.
ClassMail::SendMails()
The mails are really sent when the SendMails() methode is invoqued. I call this method as near to the end of the script(s) as possible. I decided that because I wanted the mails sending to be the ultimate action done by the script, because of difficulties I had with the hoster (online.net) while I was using mb_send_mail()... So you should call SClassMail::SendMails() and do some "print($gLocalDevString);" before the printing of "</body></html>".
This method actualy send two alternatives as message body: plain text and HTML. If the html part is an empty string (calling PrepMultiMail()), the html part is a simplistic conversion of the plain text part. If a full html string is set, this plain text part could tell to the reader that he should allow his e-mail client to display the message an HTML way, for exemple.
ClassMail::PrepMail()
The mails are stored in arrays by the method named PrepMail(). Parameters are:
$to: is a list of one or more destination e-mail addresses, possibly separated by a "\177" character,
$title: is the subject of the messsage,
$body: is the message body.
[$fotos]: is a list of pathes to pictures, séparated by "\177" character, that will be attached to the mail. The choise of picture formats is limited and predeterminated: '.gif', 'png' or 'jpeg',
[$htmlComp]: is an optional html formated text. This parameter should be assigned to 'false' if there is no picture to attach.
Call this method when you need a short message to be sent with a predeterminated body. For exemple, I use it directly to receive at home an alert message from the web site when a big change has occured related to database, or when a subscriber lost his password.
ClassMail::PrepMultiMail()
This is the general use method. Use it if the message content is composed by the site visitor. It accept a single parameter which is an array (you can get an empty array calling ClassMail::GetEmptyMailRec()). NOTE that all this fields are encoded UTF-8.
['firstnames'], ['names'] and ['email']:
Information related to the site visitor. This fields permit to compose an address of this form: '"Firstname NAME"<firstname.name@exemple.com>'
['comment'] plain text of the message,
['html'] The HTML text you want to be displayed if empty, will be replaced by an HTML form of ['comment'].
['titre'] subject of the mail
['copie'] Affect it to "true" if you want a copy to be sent to the site visitor,
['photos'] Put here a list of 1 or more pathes to pictures (.gif, .png or .jpeg), separated by a "\177" character.
['dt'] List of 1 or more percipients, separated by a "\177" character.
For those of the percipients using KMail as e-mail client, I placed an extra X-Face field you will appréciate :-)
NOTE that this X-Face string does not work when it is splited in some lines.
A journaling system had been implemented. You will probably adjust the content fonction of your needs and resources. Set E_JOURNAL_PATH constant to a path to journaling file to activate it.
Some IP addresses (from russia and china, in my case) are purely and simply rejected because some people used the server to send spams to the webmaster... See and adjust REJECTED_ADD
NOTE: The LF constant is set to "\r\n". It works well when set to "\n" only on a Linux box, but this does not correspond to ISO requirements. So, I think the best is to leave it set to "\r\n" not only because of the other world, but also for historical reasons.
TODO: "Content-Transfer-Encoding: quoted-printable": I did not succed to use : iconv("UTF-8", "UTF-8", $text) for the plain text and the HTML part. It could help to be able to read the content of the mail from source, during debugging process, but unfortunatly, the iconv() fonction adds "\n" before every '>'...
*/
define('LF', "\r\n"); // "\n" suffit pour KMail - Linux
define('X_FACE_PAT', "&R/xs<]8zev5dBfNwYTJSt\'~gTqn?tb86Q_c\$JZOiQ.wrE@,`ekpB\_={GL>m8DTg7j?'>.1BWYRZ5ugXr_)<N0#(K}eoQ4NVoh,p6O{d@N<'KdquS!7YjL]}WIZ?p4oBE!}WJP\"~z9CL8>h9l;FS_*`..'@rp,{Z7A9_Sj@%v):-1Ds%,/C+t=zu{?!#N1Uk~C,:c\$K%2vu");
define('X_FACE_DML',
"[A8/81O1Kzic5r@yH_.FH{wLAVEuJ'4I!>(6E>\"-cPFeA~tq{SHj=-2Bb!5=^5G[5tN3GIK76gm%8cRQ:\"M`\".W>)|gVj\"c-`^wW]ctm#JNHY5\"')y>`%G.WNX-fX(L7:1Si29h{}1Bw;v");
define("REJECTED_ADD","194.8.74". // 194.8.74.0 - 194.8.75.255
"|194.8.75|91.103.152". // 91.103.152.0 - 91.103.153.255
"|91.103.153|195.87.102". // 195.87.102.0 - 195.87.102.255
"|92.124.". // 92.124.32.0 - 92.124.63.255 pas de détail: 65536 supprimées!
"|89.223."); // 89.223.48.0 - 89.223.63.255 dito
class ClassMail {
/// <s"*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*">
const kMaxMails=50; /// Politique: N -> si adresses multiple (avec BCC), sinon 1 -> mails individuels.
const E_JOURNAL_PATH=''; // Change to "path/to/eLog.log" to activate journaling
const LOCAL_DEV=true; /// TODO Must be false in production !!!
const ME_AT_HOME="<regin@linux-madrid>"; // Your e-mail adresse known by your local MTA
const SITE_NAME="sourcerally.net";
private $oTo=array();
private $oTitle=array();
private $oBody=array();
private $oHtml=array();
private $oFotos=array();
private $oECnt=0;
public function SendMails () {
/// <s"*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*">
global $param;
/// Journal des e-mails: Supprimer le 1er "false" ci-dessous en cas de problème sur le site réel
mb_internal_encoding("UTF-8"); // Inutile de chercher à le mettre dans une __construct()!
$eLog=ClassMail::E_JOURNAL_PATH ? fopen(ClassMail::E_JOURNAL_PATH, 'a') : 0;
for($i=0; $i<$this->oECnt; $i++) { // mail($to, $subject, $message, $headers)
$bcc=str_replace("\177", ", ", $this->oTo[$i]);
if (($multiM=strpos($this->oTo[$i], "\177"))!==false)
$to=(ClassMail::LOCAL_DEV ? "\"no-reply\"".ClassMail::ME_AT_HOME : "\"no-reply\" <webmaster@".ClassMail::SITE_NAME.">");
else
$to=(ClassMail::LOCAL_DEV ? "\"Me At Home\"".ClassMail::ME_AT_HOME : $bcc);
$boundAlt="_alt_".($bound="_".ClassMail::SITE_NAME."_".md5(date('r', time()))."_");
$boundMix="_mix_".$bound;
$compHead="From: \"Site ".SITE_NAME."\" <webmaster@".SITE_NAME.">".LF.
"Reply-To: regin@".SITE_NAME.LF.
"X-Face: ".X_FACE_PAT.LF.
"X-Mailer: PHP_mail".LF.
($multiM ? "Bcc: ".$bcc.LF : "").
"MIME-Version: 1.0".LF;
// Le Bcc un peu caché... ·*·*·
if ($multiM) {
$tmpA0=explode("\177", $this->oTo[$i]);
for ($hiddS='', $l=count($tmpA0), $j=0; $j<$l; $j++) {
$tmpA1=explode('@', $tmpA0[$j]);
$tmpA2=explode('"', $tmpA1[0]);
$hiddS.=($hiddS ? ':' : '').$tmpA2[count($tmpA2)-1];
$tmpA2=explode('.', $tmpA1[1]);
$hiddS.='-'.$tmpA2[count($tmpA2)-1];
}
$compHead.="X-PHP-Comment: ".$hiddS.LF;
}
// Pour alléger, le journal est écrit ici. Il manque au moins "Content-type: ..."
if ($eLog)
fwrite($eLog, date("d-m-y H:i:s").
"\ndest: ".$bcc.
"\n\$oTitle: ".$this->oTitle[$i].
"\n\$oBody: ".$this->oBody[$i].
"\n\$compHead: ".$compHead.
"\n************************************\n");
/// Préparation du texte, avec alternatives texte / HTML:
$txtHead="Content-Type: multipart/alternative;".LF." boundary=\"".$boundAlt."\"";
$body2Html="<span style='color:blue;font-family:monospace;font-size:12px'>".str_replace("\n", '<br />', htmlspecialchars($this->oBody[$i], ENT_NOQUOTES, 'UTF-8'))."</span>";
$bodyTxt=
'--'.$boundAlt.LF."Content-Type: text/plain;".LF." charset=\"UTF-8\"".LF.
"Content-Transfer-Encoding: base64".LF.LF.
chunk_split(base64_encode($this->oBody[$i])).LF.
'--'.$boundAlt.LF."Content-Type: text/html;".LF." charset=\"UTF-8\"".LF.
"Content-Transfer-Encoding: base64".LF.LF.
chunk_split(base64_encode($this->oHtml[$i] ? $this->oHtml[$i] : $body2Html)).LF.
'--'.$boundAlt.'--'.LF.LF;
// Si des photos:
if ($this->oFotos[$i] && count($this->oFotos[$i])) {
$compHead.="Content-Type: Multipart/Mixed;".LF." boundary=\"".$boundMix."\""; // .LF.LF
$bodyTot='--'.$boundMix.LF.$txtHead.LF.LF.$bodyTxt; // .'--'.$boundMix.LF.LF
for ($l=count($this->oFotos[$i]), $j=0; $j<$l; $j++) {
$pi=pathinfo($this->oFotos[$i][$j]);
switch (strtolower($pi['extension'])) {
case 'gif': $imaTyp='gif'; break;
case 'png': $imaTyp='png'; break;
case 'jpg': case 'jpeg': $imaTyp='jpeg'; break;
default:
}
$bodyTot.='--'.$boundMix.LF."Content-Type: image/$imaTyp;".LF." name=\"".$pi['basename']."\"".LF."Content-Transfer-Encoding: base64".LF."Content-Disposition: attachment;".LF." filename=\"".$pi['basename']."\"".LF.LF.
chunk_split(base64_encode(file_get_contents($this->oFotos[$i][$j]))).LF; // .LF
}
$bodyTot.='--'.$boundMix."--".LF;
}
else {
$compHead.=$txtHead;
$bodyTot=$bodyTxt;
}
if (!mail(mb_encode_mimeheader($to, 'UTF-8', 'Q', LF),
mb_encode_mimeheader($this->oTitle[$i], 'UTF-8', 'Q', LF),
$bodyTot, $compHead))
{ print("\n<!-- Echec à l'exécution de mail() -->"); break; }
}
if ($eLog)
fclose($eLog);
}
public function PrepMail ($to, $title, $body, $fotos=false, $htmlComp='') {
/// <s"*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-∗">
/* Préparation simple à l'envoie d'un mail.
$to: est une suite d'adresse(s) ["Prénom NOM" <add@hotmail.com>] séparées par "\177"
$title: L'encodage se fera au moment de l'envoie réel du mail
$body: pas d'encodage ici
$fotos: array de chemins relatifs pointant des photos d'extension .gif, .png, .jpg ou .jpeg */
global $gLocalDevString;
$toArr=explode("\177", $to); // Multiples adresses...
$title="[".ClassMail::SITE_NAME."] ".$title;
while (count($toArr)) {
// placer jusqu'à kMaxMails adresses, dans $this->oTo[$i], séparé par "\177"
$i=$this->oECnt++;
for ($this->oTo[$i]='', $j=0; count($toArr) && ($j<ClassMail::kMaxMails); $j++)
$this->oTo[$i].=($j ? "\177" : '').array_pop($toArr);
$this->oTitle[$i]=$title;
$this->oBody[$i]=$body;
$this->oFotos[$i]=$fotos; // Pas de photo(s) jointe(s)
$this->oHtml[$i]=$htmlComp;
if (ClassMail::LOCAL_DEV)
$gLocalDevString.=("<br />Un mail a été virtuellement envoyé depuis le site de développement:<br />".
"To: '".htmlspecialchars($this->oTo[$i])."'<br />".
"Sujet: '".htmlspecialchars($this->oTitle[$i])."'<br />".
"Message: '".htmlspecialchars($this->oBody[$i])."'<br />---------------------------------------");
}
}
public function GetEmptyMailRec () {
/// <s"*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-∗">
return(array('firstnames'=>"", 'names'=>"", 'email'=>"", 'comment'=>"", 'html'=>"", 'titre'=>"", 'copie'=>false, 'photos'=>false, 'dt'=>""));
}
public function PrepMultiMail ($mailDat) {
/// <s"*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-∗">
/* $mailDat est une array:
['firstnames']=>'', ['names']=>'', ['email']=>'' : infos propre au membre
['titre']=>'', ['comment']=>'', ['html'],
['dt']=>'', 1 ou plus d'adresses de destination. Si > 1, séparées par "\177"
['copie']=> boolean, true envoie une copie à l'adresse ['email']
['photos']=> array de pathes relatifs pointant des photos d'extension .gif, .png, .jpg ou .jpeg
Retourne 'true' en attendant que la function mail() fonctione correctement chez online.net
$mailDat['dt'] est une suite d'adresse(s) ["Prénom NOM" <add@hotmail.com>] séparées par "\177" */
global $gObjMemb, $gVisitor, $gLocalDevString;
if ((($p0=stripos($mailDat['comment'], "http://"))!==false) && ($p0!=strpos($mailDat['comment'], "http://www.".ClassMail::SITE_NAME."/"))) {
$gLocalDevString.=ClassMail::LOCAL_DEV ? "<br /><br />Considéré comme spam.<br />\n" : "";
return(true); // Simule un bon résultat, pour tromper le spammeur
}
// Vérifier le tableau fotos:
if ($fotos=(KyNe('photos', $mailDat) && @count($mailDat['photos']))) {
$fotos=array();
foreach ($mailDat['photos'] as $v)
if (file_exists($v))
array_push($fotos, $v);
elseif (ClassMail::LOCAL_DEV)
die("<br />".basename(__FILE__)."[".__LINE__."] La photo est <b>introuvable</b>!<br />");
else
print("\n<!-- photo $v introuvable -->"); // À priori invisible!
}
/// Adresse formattée, "Prénom NOM"
$visiNameF="\"".$mailDat['firstnames']." ".$mailDat['names']."\"";
$body="from: ".$visiNameF.", ".($gVisitor->status ? "(".$gVisitor->datas['nick'].") " : "").
"site ".ClassMail::SITE_NAME.LF."**********".LF.$mailDat['comment'].LF."**********".LF;
$rejAdd=explode('|', REJECTED_ADD);
//$_SERVER['REMOTE_ADDR']="194.8.74.120"; // Essai spammeur!
foreach ($rejAdd as $v)
if (strpos($_SERVER['REMOTE_ADDR'], $v)===0)
return(true); // Simule un bon résultat pour les spammeurs
/// $this->PrepMail($to, $title, $body, $fotos=false)
$r=($this->PrepMail($mailDat['dt'], $mailDat['titre'], $body, $fotos, $mailDat['html']));
// Il est possible qu'il soit mieux d'envoyer une copie au spammer...
if ($r && $mailDat['copie']) // Envoie de la copie
$r+=$this->PrepMail($visiNameF."<".$mailDat['email'].">", $mailDat['titre']." (copy)", $body, $fotos, $mailDat['html']);
return ($r!==0);
}
}
/* multipart exemple:
$lMail['firstnames']="Noémie Mercédès";
$lMail['names']="NOVOÁ ZÖGER";
$lMail['email']="hubert@exemple.net";
$lMail['comment']="Cörp du méssage à afficher";
$lMail['html']=file_get_contents('0_mail_exemple/exemple.html');
$lMail['titre']="Quèl tïtre!";
$lMail['copie']=false;
$lMail['photos']=array('0_mail_exemple/coeur-1.png', '0_mail_exemple/coeur-2.jpg', '0_mail_exemple/coeur-3.jpeg', '0_mail_exemple/f_de.gif');
$lMail['dt']="pserru@pat.chiclayo\177\"hubert Zöger\"<dudu@pat.chiclayo>\177\"l'invité\"<invite@pat.chiclayo>";
$gObjMail->PrepMultiMail($lMail); */
?>