root/trunk/mailer.class.php

Revision 26, 26.9 kB (checked in by bobe, 2 years ago)

php_uname() prioritaire + correction formatage du numéro de version

  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?php
2 /**
3  * Copyright (c) 2002-2006 Aurélien Maille
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  * @package Wamailer
20  * @author  Bobe <wascripts@phpcodeur.net>
21  * @link    http://phpcodeur.net/wascripts/wamailer/
22  * @license http://www.gnu.org/copyleft/lesser.html
23  * @version $Id$
24  */
25
26 require dirname(__FILE__) . '/mime.class.php';
27
28 if( function_exists('email') && !function_exists('mail') ) {
29     // on est probablement chez l’hébergeur Online
30     require dirname(__FILE__) . '/online.php';
31 }
32
33 //
34 // Compatibilité php < 5.1.1
35 //
36 if( !defined('DATE_RFC2822') ) {
37     define('DATE_RFC2822', 'D, d M Y H:i:s O');
38 }
39
40 if( !($hostname = @php_uname('n')) ) {
41     $hostname = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost';
42 }
43
44 define('MAILER_HOSTNAME'$hostname);
45 define('MAILER_MIME_EOL',  (strncasecmp(PHP_OS, 'Win', 3) != 0) ? "\n" : "\r\n");
46 define('PHP_USE_SENDMAIL', (ini_get('sendmail_path') != '') ? true : false);
47 unset($hostname);
48
49 /**
50  * Classe d’envois d’emails
51  *
52  * @todo
53  * - Envoi avec SMTP (en cours)
54  * - parsing des emails sauvegardés
55  * - propagation charset dans les objets entêtes (pour encodage de param)
56  * - Ajout de Email::loadFromString() et Email::saveAsString() ?
57  *
58  * Les sources qui m’ont bien aidées :
59  *
60  * @link http://cvs.php.net/php-src/ext/standard/mail.c
61  * @link http://cvs.php.net/php-src/win32/sendmail.c
62  */
63 abstract class Mailer {
64     
65     /**
66      * Version courante de Wamailer
67      */
68     const VERSION = '3.0';
69     
70     /********************** RÉGLAGES SENDMAIL **********************/
71     
72     /**
73      * Activation du mode sendmail
74      *
75      * @var boolean
76      * @access private
77      */
78     private static $sendmail_mode = false;
79     
80     /**
81      * Commande de lancement de sendmail
82      * L’option '-t' indique au programme de récupérer les adresses des destinataires dans les
83      * en-têtes 'To', 'Cc' et 'Bcc' de l’email.
84      * L’option '-i' permet d’éviter que le programme n’interprète une ligne contenant uniquement
85      * un caractère point comme la fin du message.
86      *
87      * @var string
88      * @access public
89      */
90     public static $sendmail_cmd  = '/usr/sbin/sendmail -t -i';
91     
92     /********************** RÉGLAGES SMTP **************************/
93     
94     /**
95      * Activation du mode SMTP
96      *
97      * @var boolean
98      * @access private
99      */
100     private static $smtp_mode = false;
101     
102     /**
103      * Serveur SMTP à contacter
104      *
105      * @var string
106      * @access public
107      */
108     public static $smtp_server = 'localhost';
109     
110     /**
111      * Active ou désactive l’utilisation directe de sendmail pour l’envoi des emails
112      *
113      * @param boolean $use  Active/désactive le mode sendmail
114      *
115      * @static
116      * @access public
117      * @return void
118      */
119     public static function useSendmail($use)
120     {
121         self::$sendmail_mode = $use;
122     }
123     
124     /**
125      * Active ou désactive l’utilisation directe d’un serveur SMTP pour l’envoi des emails
126      *
127      * @param boolean $use  Active/désactive le mode SMTP
128      *
129      * @static
130      * @access public
131      * @return void
132      */
133     public static function useSMTP($use)
134     {
135         self::$smtp_mode = $use;
136     }
137     
138     /**
139      * Vérifie la validité syntaxique d'un email
140      *
141      * @param string $email
142      *
143      * @static
144      * @access public
145      * @return boolean
146      */
147     public static function checkMailSyntax($email)
148     {
149         return (bool) preg_match('/^(?:(?(?<!^)\.)[-!#$%&\'*+\/0-9=?a-z^_`{|}~]+)+@'
150             . '(?:(?(?<!@)\.)[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?)+$/i', $email);
151     }
152     
153     /**
154      * Nettoie la liste des adresses destinataires pour supprimer toute
155      * personnalisation ('My name' <my@address.tld>)
156      *
157      * @param string $addressList
158      *
159      * @static
160      * @access public
161      * @return array
162      */
163     public static function clearAddressList($addressList)
164     {
165         preg_match_all('/(?<=^|[\s,<])[-!#$%&\'*+\/0-9=?a-z^_`{|}~.]+@[-a-z0-9.]+(?=[\s,>]|$)/Si',
166             $addressList, $matches);
167         
168         return $matches[0];
169     }
170     
171     /**
172      * Envoi d’un email
173      *
174      * @param Email object $email
175      *
176      * @static
177      * @access public
178      * @return boolean
179      */
180     public static function send(Email $email)
181     {
182         $email->headers->set('X-Mailer', sprintf('Wamailer/%s', self::VERSION));
183         
184         $rPath = $email->headers->get('Return-Path');
185         if( !is_null($rPath) ) {
186             $rPath = trim($rPath->value, '<>');
187         }
188         
189         if( self::$sendmail_mode == true ) {
190             $email->headers->get('X-Mailer')->append(' (Sendmail mode)');
191             $result = self::sendmail($email->__toString(), null, $rPath);
192         }
193         else if( self::$smtp_mode == true ) {
194             if( !class_exists('Mailer_SMTP') ) {
195                 require dirname(__FILE__) . '/smtp.class.php';
196             }
197             
198             //
199             // Nous devons passer directement les adresses email des destinataires
200             // au serveur SMTP.
201             // On récupère ces adresses des entêtes To, Cc et Bcc.
202             //
203             $recipients = array();
204             foreach( array('to', 'cc', 'bcc') as $name ) {
205                 $header = $email->headers->get($name);
206                 
207                 if( !is_null($header) ) {
208                     $addressList = $header->__toString();
209                     $recipients = array_merge($recipients,
210                         self::clearAddressList($addressList));
211                 }
212             }
213             
214             $email->headers->get('X-Mailer')->append(' (SMTP mode)');
215             //
216             // L’entête Bcc ne doit pas apparaitre dans l’email envoyé.
217             // On le supprime donc.
218             //
219             $email->headers->remove('Bcc');
220             
221             $result = self::smtpmail($email->__toString(), $recipients, $rPath);
222         }
223         else {
224             $subject = $email->headers->get('Subject');
225             $recipients = $email->headers->get('To');
226             
227             list($headers, $message) = explode("\r\n\r\n", $email->__toString(), 2);
228             
229             if( !is_null($subject) ) {
230                 $subject = $subject->__toString();
231                 $headers = str_replace($subject."\r\n", '', $headers);
232                 $subject = substr($subject, 9);// on skip le nom de l’en-tête
233             }
234             
235             if( !is_null($recipients) ) {
236                 $recipients = $recipients->__toString();
237                 
238                 if( PHP_USE_SENDMAIL ) {
239                     /**
240                      * Sendmail parse les en-têtes To, Cc et Bcc s’ils sont
241                      * présents pour récupérer la liste des adresses destinataire.
242                      * On passe déjà la liste des destinataires principaux (To)
243                      * en argument de la fonction mail(), donc on supprime l’en-tête To
244                      */
245                     $headers = str_replace($recipients."\r\n", '', $headers);
246                     $recipients = substr($recipients, 4);// on skip le nom de l’en-tête
247                 }
248                 else {
249                     /**
250                      * La fonction mail() ouvre un socket vers un serveur SMTP.
251                      * On peut laisser l’en-tête To pour la personnalisation.
252                      * Il faut par contre passer une liste d’adresses débarassée
253                      * de cette personnalisation en argument de la fonction mail()
254                      * sous peine d’obtenir une erreur.
255                      */
256                     $recipients = implode(', ', self::clearAddressList($recipients));
257                 }
258             }
259             
260             if( PHP_USE_SENDMAIL ) {
261                 $headers = str_replace("\r\n", MAILER_MIME_EOL, $headers);
262                 $message = str_replace("\r\n", MAILER_MIME_EOL, $message);
263                 
264                 /**
265                  * PHP ne laisse passer les longs entêtes Subject et To que
266                  * si les plis sont séparés par des séquences \r\n<LWS>,
267                  * cela même sur les systèmes UNIX-like.
268                  * Cela semble poser problème avec certains serveurs POP ou IMAP
269                  * qui interprètent les retours chariots comme des sauts de ligne
270                  * et les remplacent comme tels, faussant ainsi le marquage de fin
271                  * du bloc d’entêtes de l’email.
272                  * On remplace les séquences \r\n<LWS> par une simple espace
273                  *
274                  * @see SKIP_LONG_HEADER_SEP routine in
275                  *   http://cvs.php.net/php-src/ext/standard/mail.c
276                  * @see PHP Bug 24805 at http://bugs.php.net/bug.php?id=24805
277                  */
278                 if( strncasecmp(PHP_OS, 'Win', 3) != 0 ) {
279                     $subject = str_replace("\r\n\t", ' ', $subject);
280                     $recipients = str_replace("\r\n\t", ' ', $recipients);
281                 }
282             }
283             else {
284                 /**
285                  * La fonction mail() utilise prioritairement la valeur de l’option
286                  * sendmail_from comme adresse à passer dans la commande MAIL FROM
287                  * (adresse qui sera utilisée par le serveur SMTP pour forger l’entête
288                  * Return-Path). On donne la valeur de $rPath à l’option sendmail_from
289                  */
290                 if( !is_null($rPath) ) {
291                     ini_set('sendmail_from', $rPath);
292                 }
293                 
294                 /**
295                  * La fonction mail() va parser elle-même les entêtes Cc et Bcc
296                  * pour passer les adresses destinataires au serveur SMTP.
297                  * Il est donc indispensable de nettoyer l’entête Cc de toute
298                  * personnalisation sous peine d’obtenir une erreur.
299                  */
300                 $header_cc = $email->headers->get('Cc');
301                 if( !is_null($header_cc) ) {
302                     $header_cc = $header_cc->__toString();
303                     $new_header_cc = new Mime_Header('Cc',
304                         implode(', ', self::clearAddressList($header_cc)));
305                     $headers = str_replace($header_cc, $new_header_cc->__toString(), $headers);
306                 }
307             }
308             
309             if( !ini_get('safe_mode') && !is_null($rPath) ) {
310                 $result = mail($recipients, $subject, $message, $headers, '-f' . $rPath);
311             }
312             else {
313                 $result = mail($recipients, $subject, $message, $headers);
314             }
315             
316             if( !PHP_USE_SENDMAIL ) {
317                 ini_restore('sendmail_from');
318             }
319         }
320         
321         return $result;
322     }
323     
324     /**
325      * Envoi via sendmail
326      *
327      * @param string $email       Email à envoyer
328      * @param string $recipients  Adresses supplémentaires de destinataires
329      * @param string $rPath       Adresse d’envoi (définit le return-path)
330      *
331      * @access public
332      * @return boolean
333      */
334     public static function sendmail($email, $recipients = null, $rPath = null)
335     {
336         if( !empty(self::$sendmail_cmd) ) {
337             $sendmail_cmd = self::$sendmail_cmd;
338         }
339         else {
340             $sendmail_cmd = ini_get('sendmail_path');
341         }
342         
343         $sendmail_path = substr($sendmail_cmd, 0, strpos($sendmail_cmd, ' '));
344         
345         if( !is_executable($sendmail_path) ) {
346             throw new Exception("Mailer::sendmail() : [$sendmail_path] n'est pas un fichier exécutable valide");
347         }
348         
349         if( !is_null($rPath) ) {
350             $sendmail_cmd .= ' -f' . escapeshellcmd($rPath);
351         }
352         
353         if( is_array($recipients) && count($recipients) > 0 ) {
354             $sendmail_cmd .= ' -- ' . escapeshellcmd(implode(' ', $recipients));
355         }
356         
357         $sendmail = popen($sendmail_cmd, 'wb');
358         fputs($sendmail, preg_replace("/\r\n?/", MAILER_MIME_EOL, $email));
359         
360         if( ($code = pclose($sendmail)) != 0 ) {
361             trigger_error("Mailer::sendmail() : Sendmail a retourné le code"
362                 . " d'erreur suivant -> $code", E_USER_WARNING);
363             return false;
364         }
365         
366         return true;
367     }
368     
369     /**
370      * Envoi via la classe smtp
371      *
372      * @param string $email       Email à envoyer
373      * @param string $recipients  Adresses des destinataires
374      * @param string $rPath       Adresse d’envoi (définit le return-path)
375      *
376      * @access public
377      * @return boolean
378      */
379     public static function smtpmail($email, $recipients, $rPath = null)
380     {
381         if( !class_exists('Mailer_SMTP') ) {
382             require dirname(__FILE__) . '/smtp.class.php';
383         }
384         
385         if( is_null($rPath) ) {
386             $rPath = ini_get('sendmail_from');
387         }
388         
389         $smtp = new Mailer_SMTP();
390         $smtp->connect(self::$smtp_server);
391         
392         if( !$smtp->from($rPath) ) {
393 //            $this->error($smtp->msg_error);
394             $GLOBALS['php_errormsg'] = $smtp->responseData;
395             $smtp->quit();
396             return false;
397         }
398         
399         foreach( $recipients as $recipient ) {
400             if( !$smtp->to($recipient) ) {
401 //                $this->error($smtp->msg_error);
402                 $GLOBALS['php_errormsg'] = $smtp->responseData;
403                 $smtp->quit();
404                 return false;
405             }
406         }
407         
408         if( !$smtp->send($email) ) {
409 //            $this->error($smtp->msg_error);
410             $GLOBALS['php_errormsg'] = $smtp->responseData;
411             $smtp->quit();
412             return false;
413         }
414         
415         $smtp->quit();
416         
417         return true;
418     }
419     
420     public static function setError() {}
421     public static function getError() { return $GLOBALS['php_errormsg']; }
422 }
423
424 class Email {
425     /**
426      * Liste de constantes utilisables avec la méthode Email::setPriority()
427      */
428     const PRIORITY_HIGHEST = 1;
429     const PRIORITY_HIGH    = 2;
430     const PRIORITY_NORMAL  = 3;
431     const PRIORITY_LOW     = 4;
432     const PRIORITY_LOWEST  = 5;
433     
434     /**
435      * Email du destinataire
436      *
437      * @var string
438      * @access protected
439      */
440     protected $sender = '';
441     
442     /**
443      * Jeu de caractères générique de l’email (D’autres jeux peuvent être
444      * ultérieurement définis pour des sous-ensembles de l’email)
445      *
446      * @var string
447      * @access public
448      */
449     public $charset = 'ISO-8859-1';
450     
451     /**
452      * Bloc d’en-têtes de l’email
453      *
454      * @var object
455      * @see Mime_Headers class
456      * @access protected
457      */
458     protected $_headers = null;
459     
460     /**
461      * Partie texte brut de l’email
462      *
463      * @var object
464      * @see Mime_Part class
465      * @access protected
466      */
467     protected $_textPart = null;
468     
469     /**
470      * Partie HTML de l’email
471      *
472      * @var object
473      * @see Mime_Part class
474      * @access protected
475      */
476     protected $_htmlPart = null;
477     
478     /**
479      * Multi-Partie globale de l’email
480      *
481      * @var array
482      * @access protected
483      */
484     protected $_attachParts = array();
485     
486     /**
487      * @var string
488      * @access protected
489      */
490     protected $headers_txt = '';
491     
492     /**
493      * @var string
494      * @access protected
495      */
496     protected $message_txt = '';
497     
498     /**
499      * Constructeur de classe
500      *
501      * @access public
502      * @return void
503      */
504     public function __construct($charset = null)
505     {
506         $this->_headers = new Mime_Headers(array(
507             'Return-Path' => '',
508             'Date' => '',
509             'From' => '',
510             'Sender' => '',
511             'Reply-To' => '',
512             'To' => '',
513             'Cc' => '',
514             'Bcc' => '',
515             'Subject' => '',
516             'Message-ID' => '',
517             'MIME-Version' => ''
518         ));
519         
520         if( !is_null($charset) ) {
521             $this->charset = $charset;
522         }
523     }
524     
525     /**
526      * Charge un email à partir d’un fichier mail
527      *
528      * @param string  $filename
529      * @param boolean $fullParse
530      *
531      * @access public
532      * @return mixed
533      */
534     public function load($filename, $fullParse = false)
535     {
536         if( !is_readable($filename) ) {
537             throw new Exception("Cannot read file '$filename'");
538         }
539         
540         $input = file_get_contents($filename);
541         $input = preg_replace('/\r\n?|\n/', "\r\n", $input);
542         list($headers, $message) = explode("\r\n\r\n", $input, 2);
543         
544         if( !isset($this) || !($this instanceof Email) ) {
545             $email = new Email();
546             $returnBool = false;
547         }
548         else {
549             $email = $this;
550             $returnBool = true;
551         }
552         
553         $headers = preg_split("/\r\n(?![\x09\x20])/", $headers);
554         foreach( $headers as $header ) {
555             if( strpos($header, ':') ) {// Pour esquiver l’éventuelle ligne From - ...
556                 list($name, $value) = explode(':', $header, 2);
557                 $email->headers->add($name, $value);
558             }
559         }
560         
561 /*        if( $fullParse ) {
562             $sender = $email->headers->get('Return-Path');
563             if( !is_null($sender) ) {
564                 $email->sender = trim($sender->value, '<>');
565             }
566             
567             // @todo
568             // Récupération charset "global"
569             // + structure, si compatible
570             // + attention, headers du premier mime_part se trouvent dans
571             // le Mime_Headers de l’objet Email
572             
573             $contentType = $email->headers->get('Content-Type');
574             $boundary = $contentType->param('boundary');
575             $charset  = $contentType->param('charset');
576             
577             if( !is_null($contentType) && strncasecmp($contentType->value, 'multipart', 9) == 0 ) {
578                 if( is_null($boundary) ) {
579                     throw new Exception("Bad mime part (missed boundary)");
580                 }
581                 
582                 
583             }
584             else {
585                 switch( $contentType->value ) {
586                     case 'text/plain':
587                         $email->setTextBody($message, $charset);
588                         break;
589                     case 'text/html':
590                         $email->setHTMLBody($message, $charset);
591                         brea