4 * (c) 2009-2011 Collectif SPIP
5 * Distribue sous licence GPL
9 if (!defined("_ECRIRE_INC_VERSION")) return;
11 include_spip('inc/charsets');
12 include_spip('inc/texte');
13 include_spip('inc/filtres');
15 if (!class_exists('PHPMailer')) {
16 include_spip('phpmailer-php5/class.phpmailer');
17 include_spip('phpmailer-php5/class.smtp');
20 include_spip('facteur_fonctions');
23 * Wrapper de spip_log pour par PHPMailer
27 function facteur_log_debug($message,$level){
28 spip_log("$level: ".trim($message),"facteur"._LOG_DEBUG
);
32 class Facteur
extends PHPMailer
{
34 * From force si From pas dans le bon domaine
37 public $ForceFrom = '';
40 * FromName force si From pas dans le bon domaine
43 public $ForceFromName = '';
48 * @param $message_html
49 * @param $message_texte
50 * @param array $options
53 public function __construct($email, $objet, $message_html, $message_texte, $options = array()) {
54 // On récupère toutes les options par défaut depuis le formulaire de config
57 'adresse_envoi', 'adresse_envoi_email', 'adresse_envoi_nom', 'forcer_from',
59 'smtp', 'smtp_host', 'smtp_port', 'smtp_auth',
60 'smtp_username', 'smtp_password', 'smtp_secure', 'smtp_sender', 'smtp_tls_allow_self_signed',
61 'filtre_images', 'filtre_iso_8859',
63 $defaut[$config] = isset($GLOBALS['meta']["facteur_$config"]) ?
$GLOBALS['meta']["facteur_$config"] : '';
65 // On fusionne les options avec d'éventuelles surcharges lors de l'appel
66 $options = array_merge($defaut, $options);
68 // par defaut on log rien car tres verbeux
69 // on utilise facteur_log_debug qui filtre log SPIP en _LOG_DEBUG
71 $this->Debugoutput
= "facteur_log_debug";
72 // Il est possible d'avoir beaucoup plus de logs avec 2, 3 ou 4, ce qui logs les échanges complets avec le serveur
73 // utiliser avec un define('_MAX_LOG',1000); car sinon on est limite a 100 lignes par hit et phpMailer est tres verbeux
74 if (defined('_FACTEUR_DEBUG_SMTP')) {
75 $this->SMTPDebug
= _FACTEUR_DEBUG_SMTP
;
77 $this->exceptions
= false;
81 $options['adresse_envoi'] == 'oui'
82 and $options['adresse_envoi_email']
84 $this->From
= $options['adresse_envoi_email'];
87 $this->From
= (isset($GLOBALS['meta']["email_envoi"]) AND $GLOBALS['meta']["email_envoi"]) ?
88 $GLOBALS['meta']["email_envoi"]
89 : $GLOBALS['meta']['email_webmaster'];
92 // Si plusieurs emails dans le from, pas de Name !
93 if (strpos($this->From
,",") === false) {
95 $options['adresse_envoi'] == 'oui'
96 and $options['adresse_envoi_nom']
98 $this->FromName
= $options['adresse_envoi_nom'];
100 // Par défaut, l'envoyeur est le nom du site
102 $this->FromName
= strip_tags(extraire_multi($GLOBALS['meta']['nom_site']));
106 // si forcer_from, on sauvegarde le From et FromName par defaut, qui seront utilises
107 // si From n'est pas dans le meme domaine
108 // (utiliser le facteur avec un service externe qui necessite la validation des domaines d'envoi)
109 if ($options['forcer_from']=='oui'){
110 $this->ForceFrom
= $this->From
;
111 $this->ForceFromName
= $this->FromName
;
114 $this->CharSet
= "utf-8";
115 $this->Mailer
= 'mail';
116 $this->Subject
= unicode_to_utf_8(charset2unicode($objet,$GLOBALS['meta']['charset']));
118 //Pour un envoi multiple de mail, $email doit être un tableau avec les adresses.
119 if (is_array($email)) {
120 foreach ($email as $cle => $adresseMail) {
121 if (!$this->AddAddress($adresseMail)) {
122 spip_log("Erreur AddAddress $adresseMail : ".print_r($this->ErrorInfo
, true), 'facteur.'._LOG_ERREUR
);
126 elseif (!$this->AddAddress($email)) {
127 spip_log("Erreur AddAddress $email : ".print_r($this->ErrorInfo
, true), 'facteur.'._LOG_ERREUR
);
130 // Retour des erreurs
131 if (!empty($options['smtp_sender'])) {
132 $this->Sender
= $options['smtp_sender'];
133 $this->AddCustomHeader("Errors-To: ".$this->Sender
);
136 // Destinataires en copie, seulement s'il n'y a pas de destinataire de test
137 if (!defined('_TEST_EMAIL_DEST')){
138 if (!empty($options['cc'])) {
139 $this->AddCC($options['cc']);
141 if (!empty($options['bcc'])) {
142 $this->AddBCC($options['bcc']);
146 // Si on envoie avec un SMTP explicite
147 if (isset($options['smtp']) AND $options['smtp'] == 'oui') {
148 $this->Mailer
= 'smtp';
149 $this->Host
= $options['smtp_host'];
150 $this->Port
= $options['smtp_port'];
153 if ($options['smtp_auth'] == 'oui') {
154 $this->SMTPAuth
= true;
155 $this->Username
= $options['smtp_username'];
156 $this->Password
= $options['smtp_password'];
159 $this->SMTPAuth
= false;
162 if ($options['smtp_secure'] == 'ssl') {
163 $this->SMTPSecure
= 'ssl';
165 if ($options['smtp_secure'] == 'tls') {
166 $this->SMTPSecure
= 'tls';
169 if ( $options['smtp_secure'] == 'tls' && $options['smtp_tls_allow_self_signed'] == 'oui' ) {
170 $this->SMTPOptions
= array(
171 'ssl' => array('allow_self_signed' => true)
175 // Pour le moment on remet l'ancien fonctionnement :
176 // on ne doit pas tester les certificats si pas demandé explicitement avec l'option TLS !
177 $this->SMTPAutoTLS
= false;
180 // S'il y a un contenu HTML
181 if (!empty($message_html)) {
182 $message_html = unicode_to_utf_8(charset2unicode($message_html, $GLOBALS['meta']['charset']));
184 $this->Body
= $message_html;
186 if ($options['filtre_images']) {
187 $this->JoindreImagesHTML();
190 $this->UrlsAbsolues();
193 // S'il y a un contenu texte brut
194 if (!empty($message_texte)) {
195 $message_texte = unicode_to_utf_8(charset2unicode($message_texte, $GLOBALS['meta']['charset']));
197 // Si pas de HTML on le remplace en tant que contenu principal
199 $this->IsHTML(false);
200 $this->Body
= $message_texte;
202 // Sinon on met le texte brut en contenu alternatif
204 $this->AltBody
= $message_texte;
208 if ($options['filtre_iso_8859']) {
209 $this->ConvertirUtf8VersIso8859();
214 * @param bool $exceptions
216 public function SetExceptions($exceptions){
217 $this->exceptions
= ($exceptions?
true:false);
221 * Transforme du HTML en texte brut, mais proprement
222 * utilise le filtre facteur_mail_html2text
223 * @uses facteur_mail_html2text()
225 * @param string $html Le HTML à transformer
226 * @param bool $advanced Inutilisé
227 * @return string Retourne un texte brut formaté correctement
229 public function html2text($html, $advanced = false){
230 return facteur_mail_html2text($html);
234 * Compat ascendante, obsolete
237 public function ConvertirStylesEnligne() {
238 $this->Body
= facteur_convertir_styles_inline($this->Body
);
242 * Transformer les urls des liens et des images en url absolues
243 * sans toucher aux images embarquees de la forme "cid:..."
245 protected function UrlsAbsolues($base=null){
246 include_spip('inc/filtres_mini');
247 if (preg_match_all(',(<(a|link)[[:space:]]+[^<>]*href=["\']?)([^"\' ><[:space:]]+)([^<>]*>),imsS',
248 $this->Body
, $liens, PREG_SET_ORDER
)) {
249 foreach ($liens as $lien) {
250 if (strncmp($lien[3],"cid:",4)!==0){
251 $abs = url_absolue($lien[3], $base);
252 if ($abs <> $lien[3] and !preg_match('/^#/',$lien[3]))
253 $this->Body
= str_replace($lien[0], $lien[1].$abs.$lien[4], $this->Body
);
257 if (preg_match_all(',(<(img|script)[[:space:]]+[^<>]*src=["\']?)([^"\' ><[:space:]]+)([^<>]*>),imsS',
258 $this->Body
, $liens, PREG_SET_ORDER
)) {
259 foreach ($liens as $lien) {
260 if (strncmp($lien[3],"cid:",4)!==0){
261 $abs = url_absolue($lien[3], $base);
262 if ($abs <> $lien[3])
263 $this->Body
= str_replace($lien[0], $lien[1].$abs.$lien[4], $this->Body
);
270 * Embed les images HTML dans l'email
272 protected function JoindreImagesHTML() {
273 $image_types = array(
274 'gif' => 'image/gif',
275 'jpg' => 'image/jpeg',
276 'jpeg' => 'image/jpeg',
277 'jpe' => 'image/jpeg',
278 'bmp' => 'image/bmp',
279 'png' => 'image/png',
280 'tif' => 'image/tiff',
281 'tiff' => 'image/tiff',
282 'swf' => 'application/x-shockwave-flash'
284 $src_found = array();
285 $images_embeded = array();
287 '/["\'](([^"\']+)\.('.implode('|', array_keys($image_types)).'))([?][^"\']+)?([#][^"\']+)?["\']/Uims',
288 $this->Body
, $images, PREG_SET_ORDER
)) {
290 $adresse_site = $GLOBALS['meta']['adresse_site'].'/';
291 foreach($images as $im){
292 $im = array_pad($im, 6, null);
293 $src_orig = $im[1].$im[4].$im[5];
294 if (!isset($src_found[$src_orig])){ // deja remplace ? rien a faire (ie la meme image presente plusieurs fois)
295 // examiner le src et voir si embedable
297 if ($src AND strncmp($src,$adresse_site,strlen($adresse_site))==0)
298 $src = _DIR_RACINE
. substr($src,strlen($adresse_site));
300 AND !preg_match(",^[a-z0-9]+://,i",$src)
302 file_exists($f=$src) // l'image a ete generee depuis le meme cote que l'envoi
303 OR (_DIR_RACINE
AND file_exists($f=_DIR_RACINE
.$src)) // l'image a ete generee dans le public et on est dans le prive
304 OR (!_DIR_RACINE
AND file_exists($f=_DIR_RESTREINT
.$src)) // l'image a ete generee dans le prive et on est dans le public
307 if (!isset($images_embeded[$f])){
308 $extension = strtolower($im[3]);
309 $header_extension = $image_types[$extension];
310 $cid = md5($f); // un id unique pour un meme fichier
311 $images_embeded[$f] = $cid; // marquer l'image comme traitee, inutile d'y revenir
312 $this->AddEmbeddedImage($f, $cid, basename($f),'base64',$header_extension);
315 $this->Body
= str_replace($src_orig, "cid:".$images_embeded[$f], $this->Body
);
316 $src_found[$src_orig] = $f;
325 * Conversion safe d'un texte utf en isotruc
326 * @param string $text
327 * @param string $mode
330 protected function safe_utf8_decode($text,$mode='texte_brut') {
334 if (function_exists('iconv') && $mode == 'texte_brut') {
335 $text = str_replace('’',"'",$text);
336 $text = iconv("UTF-8", "ISO-8859-1//TRANSLIT", $text);
337 return str_replace('’',"'",$text);
340 if ($mode == 'texte_brut') {
341 $text = str_replace('’',"'",$text);
343 $text = unicode2charset(utf_8_to_unicode($text),'iso-8859-1');
344 return str_replace('’',"'",$text);
349 * Convertir tout le mail utf en isotruc
351 protected function ConvertirUtf8VersIso8859() {
352 $this->CharSet
= 'iso-8859-1';
353 $this->Body
= str_ireplace('charset=utf-8', 'charset=iso-8859-1', $this->Body
);
354 $this->Body
= $this->safe_utf8_decode($this->Body
,'html');
355 $this->AltBody
= $this->safe_utf8_decode($this->AltBody
);
356 $this->Subject
= $this->safe_utf8_decode($this->Subject
);
357 $this->FromName
= $this->safe_utf8_decode($this->FromName
);
361 * Convertir les accents du body en entites html
363 protected function ConvertirAccents() {
364 // tableau à compléter au fur et à mesure
385 $this->Body
= strtr($this->Body
, $cor);
390 * Une fonction wrapper pour appeler une methode de phpMailer
391 * en recuperant l'erreur eventuelle, en la loguant via SPIP et en lancant une exception si demandee
392 * @param string $function
395 * @throws phpmailerException
397 protected function callWrapper($function,$args){
398 $exceptions = $this->exceptions
;
399 $this->exceptions
= true;
401 $retour = call_user_func_array($function,$args);
402 $this->exceptions
= $exceptions;
404 catch (phpmailerException
$exc) {
405 spip_log((is_array($function)?
implode('::',$function):$function)."() : ".$exc->getMessage(),'facteur.'._LOG_ERREUR
);
406 $this->exceptions
= $exceptions;
407 if ($this->exceptions
) {
412 if ($this->ErrorInfo
){
413 spip_log((is_array($function)?
implode('::',$function):$function)."() : ".$this->ErrorInfo
,'facteur.'._LOG_ERREUR
);
420 * Appel des fonctions parents via le callWrapper qui se charge de loger les erreurs
424 * Avant le Send() on force le From si besoin
426 * @throws phpmailerException
428 public function Send() {
430 AND $this->From
!==$this->ForceFrom
){
431 $forcedomain = explode('@',$this->ForceFrom
);
432 $forcedomain = end($forcedomain);
433 $domain = explode('@',$this->From
);
434 $domain = end($domain);
435 if ($domain!==$forcedomain){
436 // le From passe en ReplyTo
437 $this->AddReplyTo($this->From
,$this->FromName
);
439 $this->From
= $this->ForceFrom
;
440 $this->FromName
= $this->ForceFromName
;
443 $args = func_get_args();
444 return $this->callWrapper(array('parent','Send'),$args);
446 public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') {
447 $args = func_get_args();
448 return $this->callWrapper(array('parent','AddAttachment'),$args);
450 public function AddReplyTo($address, $name = '') {
451 $args = func_get_args();
452 return $this->callWrapper(array('parent','AddReplyTo'),$args);
454 public function AddBCC($address, $name = '') {
455 $args = func_get_args();
456 return $this->callWrapper(array('parent','AddBCC'),$args);
458 public function AddCC($address, $name = '') {
459 $args = func_get_args();
460 return $this->callWrapper(array('parent','AddCC'),$args);