46b5dee273f54b97011a3e3dc8e24fa70edf1b3d
[lhc/web/www.git] / www / plugins / facteur / classes / facteur.php
1 <?php
2 /*
3 * Plugin Facteur 2
4 * (c) 2009-2011 Collectif SPIP
5 * Distribue sous licence GPL
6 *
7 */
8
9 if (!defined("_ECRIRE_INC_VERSION")) return;
10
11 include_spip('inc/charsets');
12 include_spip('inc/texte');
13 include_spip('inc/filtres');
14
15 if (!class_exists('PHPMailer')) {
16 include_spip('phpmailer-php5/class.phpmailer');
17 include_spip('phpmailer-php5/class.smtp');
18 }
19
20 include_spip('facteur_fonctions');
21
22 /**
23 * Wrapper de spip_log pour par PHPMailer
24 * @param $message
25 * @param $level
26 */
27 function facteur_log_debug($message,$level){
28 spip_log("$level: ".trim($message),"facteur"._LOG_DEBUG);
29 }
30
31
32 class Facteur extends PHPMailer {
33 /**
34 * From force si From pas dans le bon domaine
35 * @var string
36 */
37 public $ForceFrom = '';
38
39 /**
40 * FromName force si From pas dans le bon domaine
41 * @var string
42 */
43 public $ForceFromName = '';
44
45 /**
46 * @param $email
47 * @param $objet
48 * @param $message_html
49 * @param $message_texte
50 * @param array $options
51 *
52 */
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
55 $defaut = array();
56 foreach (array(
57 'adresse_envoi', 'adresse_envoi_email', 'adresse_envoi_nom', 'forcer_from',
58 'cc', 'bcc',
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',
62 ) as $config) {
63 $defaut[$config] = isset($GLOBALS['meta']["facteur_$config"]) ? $GLOBALS['meta']["facteur_$config"] : '';
64 }
65 // On fusionne les options avec d'éventuelles surcharges lors de l'appel
66 $options = array_merge($defaut, $options);
67
68 // par defaut on log rien car tres verbeux
69 // on utilise facteur_log_debug qui filtre log SPIP en _LOG_DEBUG
70 $this->SMTPDebug = 0;
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 ;
76 }
77 $this->exceptions = false;
78
79
80 if (
81 $options['adresse_envoi'] == 'oui'
82 and $options['adresse_envoi_email']
83 ) {
84 $this->From = $options['adresse_envoi_email'];
85 }
86 else {
87 $this->From = (isset($GLOBALS['meta']["email_envoi"]) AND $GLOBALS['meta']["email_envoi"]) ?
88 $GLOBALS['meta']["email_envoi"]
89 : $GLOBALS['meta']['email_webmaster'];
90 }
91
92 // Si plusieurs emails dans le from, pas de Name !
93 if (strpos($this->From,",") === false) {
94 if (
95 $options['adresse_envoi'] == 'oui'
96 and $options['adresse_envoi_nom']
97 ) {
98 $this->FromName = $options['adresse_envoi_nom'];
99 }
100 // Par défaut, l'envoyeur est le nom du site
101 else {
102 $this->FromName = strip_tags(extraire_multi($GLOBALS['meta']['nom_site']));
103 }
104 }
105
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;
112 }
113
114 $this->CharSet = "utf-8";
115 $this->Mailer = 'mail';
116 $this->Subject = unicode_to_utf_8(charset2unicode($objet,$GLOBALS['meta']['charset']));
117
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);
123 }
124 }
125 }
126 elseif (!$this->AddAddress($email)) {
127 spip_log("Erreur AddAddress $email : ".print_r($this->ErrorInfo, true), 'facteur.'._LOG_ERREUR);
128 }
129
130 // Retour des erreurs
131 if (!empty($options['smtp_sender'])) {
132 $this->Sender = $options['smtp_sender'];
133 $this->AddCustomHeader("Errors-To: ".$this->Sender);
134 }
135
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']);
140 }
141 if (!empty($options['bcc'])) {
142 $this->AddBCC($options['bcc']);
143 }
144 }
145
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'];
151
152 // SMTP authentifié
153 if ($options['smtp_auth'] == 'oui') {
154 $this->SMTPAuth = true;
155 $this->Username = $options['smtp_username'];
156 $this->Password = $options['smtp_password'];
157 }
158 else {
159 $this->SMTPAuth = false;
160 }
161
162 if ($options['smtp_secure'] == 'ssl') {
163 $this->SMTPSecure = 'ssl';
164 }
165 if ($options['smtp_secure'] == 'tls') {
166 $this->SMTPSecure = 'tls';
167 }
168
169 if ( $options['smtp_secure'] == 'tls' && $options['smtp_tls_allow_self_signed'] == 'oui' ) {
170 $this->SMTPOptions = array(
171 'ssl' => array('allow_self_signed' => true)
172 );
173 }
174
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;
178 }
179
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']));
183
184 $this->Body = $message_html;
185 $this->IsHTML(true);
186 if ($options['filtre_images']) {
187 $this->JoindreImagesHTML();
188 }
189
190 $this->UrlsAbsolues();
191 }
192
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']));
196
197 // Si pas de HTML on le remplace en tant que contenu principal
198 if (!$this->Body) {
199 $this->IsHTML(false);
200 $this->Body = $message_texte;
201 }
202 // Sinon on met le texte brut en contenu alternatif
203 else {
204 $this->AltBody = $message_texte;
205 }
206 }
207
208 if ($options['filtre_iso_8859']) {
209 $this->ConvertirUtf8VersIso8859();
210 }
211 }
212
213 /**
214 * @param bool $exceptions
215 */
216 public function SetExceptions($exceptions){
217 $this->exceptions = ($exceptions?true:false);
218 }
219
220 /**
221 * Transforme du HTML en texte brut, mais proprement
222 * utilise le filtre facteur_mail_html2text
223 * @uses facteur_mail_html2text()
224 *
225 * @param string $html Le HTML à transformer
226 * @param bool $advanced Inutilisé
227 * @return string Retourne un texte brut formaté correctement
228 */
229 public function html2text($html, $advanced = false){
230 return facteur_mail_html2text($html);
231 }
232
233 /**
234 * Compat ascendante, obsolete
235 * @deprecated
236 */
237 public function ConvertirStylesEnligne() {
238 $this->Body = facteur_convertir_styles_inline($this->Body);
239 }
240
241 /**
242 * Transformer les urls des liens et des images en url absolues
243 * sans toucher aux images embarquees de la forme "cid:..."
244 */
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);
254 }
255 }
256 }
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);
264 }
265 }
266 }
267 }
268
269 /**
270 * Embed les images HTML dans l'email
271 */
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'
283 );
284 $src_found = array();
285 $images_embeded = array();
286 if (preg_match_all(
287 '/["\'](([^"\']+)\.('.implode('|', array_keys($image_types)).'))([?][^"\']+)?([#][^"\']+)?["\']/Uims',
288 $this->Body, $images, PREG_SET_ORDER)) {
289
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
296 $src = $im[1];
297 if ($src AND strncmp($src,$adresse_site,strlen($adresse_site))==0)
298 $src = _DIR_RACINE . substr($src,strlen($adresse_site));
299 if ($src
300 AND !preg_match(",^[a-z0-9]+://,i",$src)
301 AND (
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
305 )
306 ){
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);
313 }
314
315 $this->Body = str_replace($src_orig, "cid:".$images_embeded[$f], $this->Body);
316 $src_found[$src_orig] = $f;
317 }
318 }
319 }
320 }
321 }
322
323
324 /**
325 * Conversion safe d'un texte utf en isotruc
326 * @param string $text
327 * @param string $mode
328 * @return string
329 */
330 protected function safe_utf8_decode($text,$mode='texte_brut') {
331 if (!is_utf8($text))
332 return ($text);
333
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('&#8217;',"'",$text);
338 }
339 else {
340 if ($mode == 'texte_brut') {
341 $text = str_replace('’',"'",$text);
342 }
343 $text = unicode2charset(utf_8_to_unicode($text),'iso-8859-1');
344 return str_replace('&#8217;',"'",$text);
345 }
346 }
347
348 /**
349 * Convertir tout le mail utf en isotruc
350 */
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);
358 }
359
360 /**
361 * Convertir les accents du body en entites html
362 */
363 protected function ConvertirAccents() {
364 // tableau à compléter au fur et à mesure
365 $cor = array(
366 'à' => '&agrave;',
367 'â' => '&acirc;',
368 'ä' => '&auml;',
369 'ç' => '&ccedil;',
370 'é' => '&eacute;',
371 'è' => '&egrave;',
372 'ê' => '&ecirc;',
373 'ë' => '&euml;',
374 'î' => '&icirc;',
375 'ï' => '&iuml;',
376 'ò' => '&ograve;',
377 'ô' => '&ocirc;',
378 'ö' => '&ouml;',
379 'ù' => '&ugrave;',
380 'û' => '&ucirc;',
381 'œ' => '&oelig;',
382 '€' => '&euro;'
383 );
384
385 $this->Body = strtr($this->Body, $cor);
386 }
387
388
389 /**
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
393 * @param array $args
394 * @return bool
395 * @throws phpmailerException
396 */
397 protected function callWrapper($function,$args){
398 $exceptions = $this->exceptions;
399 $this->exceptions = true;
400 try {
401 $retour = call_user_func_array($function,$args);
402 $this->exceptions = $exceptions;
403 }
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) {
408 throw $exc;
409 }
410 return false;
411 }
412 if ($this->ErrorInfo){
413 spip_log((is_array($function)?implode('::',$function):$function)."() : ".$this->ErrorInfo,'facteur.'._LOG_ERREUR);
414 }
415
416 return $retour;
417 }
418
419 /*
420 * Appel des fonctions parents via le callWrapper qui se charge de loger les erreurs
421 */
422
423 /**
424 * Avant le Send() on force le From si besoin
425 * @return bool
426 * @throws phpmailerException
427 */
428 public function Send() {
429 if ($this->ForceFrom
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);
438 // on force le From
439 $this->From = $this->ForceFrom;
440 $this->FromName = $this->ForceFromName;
441 }
442 }
443 $args = func_get_args();
444 return $this->callWrapper(array('parent','Send'),$args);
445 }
446 public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') {
447 $args = func_get_args();
448 return $this->callWrapper(array('parent','AddAttachment'),$args);
449 }
450 public function AddReplyTo($address, $name = '') {
451 $args = func_get_args();
452 return $this->callWrapper(array('parent','AddReplyTo'),$args);
453 }
454 public function AddBCC($address, $name = '') {
455 $args = func_get_args();
456 return $this->callWrapper(array('parent','AddBCC'),$args);
457 }
458 public function AddCC($address, $name = '') {
459 $args = func_get_args();
460 return $this->callWrapper(array('parent','AddCC'),$args);
461 }
462 }