1<?php
2////////////////////////////////////////////////////
3// PHPMailer - PHP email class
4//
5// Class for sending email using either
6// sendmail, PHP mail(), or SMTP.  Methods are
7// based upon the standard AspEmail(tm) classes.
8//
9// Copyright (C) 2001 - 2003  Brent R. Matzelle
10//
11// License: LGPL, see LICENSE
12////////////////////////////////////////////////////
13
14/**
15 * PHPMailer - PHP email transport class
16 * @package PHPMailer
17 * @author Brent R. Matzelle
18 * @copyright 2001 - 2003 Brent R. Matzelle
19 */
20class PHPMailer
21{
22    /////////////////////////////////////////////////
23    // PUBLIC VARIABLES
24    /////////////////////////////////////////////////
25
26    /**
27     * Email priority (1 = High, 3 = Normal, 5 = low).
28     * @var int
29     */
30    var $Priority          = 3;
31
32    /**
33     * Sets the CharSet of the message.
34     * @var string
35     */
36    var $CharSet           = "iso-8859-1";
37
38    /**
39     * Sets the Content-type of the message.
40     * @var string
41     */
42    var $ContentType        = "text/plain";
43
44    /**
45     * Sets the Encoding of the message. Options for this are "8bit",
46     * "7bit", "binary", "base64", and "quoted-printable".
47     * @var string
48     */
49    var $Encoding          = "8bit";
50
51    /**
52     * Holds the most recent mailer error message.
53     * @var string
54     */
55    var $ErrorInfo         = "";
56
57    /**
58     * Sets the From email address for the message.
59     * @var string
60     */
61    var $From               = "root@localhost";
62
63    /**
64     * Sets the From name of the message.
65     * @var string
66     */
67    var $FromName           = "Root User";
68
69    /**
70     * Sets the Sender email (Return-Path) of the message.  If not empty,
71     * will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
72     * @var string
73     */
74    var $Sender            = "";
75
76    /**
77     * Sets the Subject of the message.
78     * @var string
79     */
80    var $Subject           = "";
81
82    /**
83     * Sets the Body of the message.  This can be either an HTML or text body.
84     * If HTML then run IsHTML(true).
85     * @var string
86     */
87    var $Body               = "";
88
89    /**
90     * Sets the text-only body of the message.  This automatically sets the
91     * email to multipart/alternative.  This body can be read by mail
92     * clients that do not have HTML email capability such as mutt. Clients
93     * that can read HTML will view the normal Body.
94     * @var string
95     */
96    var $AltBody           = "";
97
98    /**
99     * Sets word wrapping on the body of the message to a given number of
100     * characters.
101     * @var int
102     */
103    var $WordWrap          = 0;
104
105    /**
106     * Method to send mail: ("mail", "sendmail", or "smtp").
107     * @var string
108     */
109    var $Mailer            = "mail";
110
111    /**
112     * Sets the path of the sendmail program.
113     * @var string
114     */
115    var $Sendmail          = "/usr/sbin/sendmail";
116
117    /**
118     * Path to PHPMailer plugins.  This is now only useful if the SMTP class
119     * is in a different directory than the PHP include path.
120     * @var string
121     */
122    var $PluginDir         = "";
123
124    /**
125     *  Holds PHPMailer version.
126     *  @var string
127     */
128    var $Version           = "1.72";
129
130    /**
131     * Sets the email address that a reading confirmation will be sent.
132     * @var string
133     */
134    var $ConfirmReadingTo  = "";
135
136    /**
137     *  Sets the hostname to use in Message-Id and Received headers
138     *  and as default HELO string. If empty, the value returned
139     *  by SERVER_NAME is used or 'localhost.localdomain'.
140     *  @var string
141     */
142    var $Hostname          = "";
143
144    /////////////////////////////////////////////////
145    // SMTP VARIABLES
146    /////////////////////////////////////////////////
147
148    /**
149     *  Sets the SMTP hosts.  All hosts must be separated by a
150     *  semicolon.  You can also specify a different port
151     *  for each host by using this format: [hostname:port]
152     *  (e.g. "smtp1.example.com:25;smtp2.example.com").
153     *  Hosts will be tried in order.
154     *  @var string
155     */
156    var $Host        = "localhost";
157
158    /**
159     *  Sets the default SMTP server port.
160     *  @var int
161     */
162    var $Port        = 25;
163
164    /**
165     *  Sets the SMTP HELO of the message (Default is $Hostname).
166     *  @var string
167     */
168    var $Helo        = "";
169
170    /**
171     *  Sets SMTP authentication. Utilizes the Username and Password variables.
172     *  @var bool
173     */
174    var $SMTPAuth     = false;
175
176    /**
177     *  Sets SMTP username.
178     *  @var string
179     */
180    var $Username     = "";
181
182    /**
183     *  Sets SMTP password.
184     *  @var string
185     */
186    var $Password     = "";
187
188    /**
189     *  Sets the SMTP server timeout in seconds. This function will not
190     *  work with the win32 version.
191     *  @var int
192     */
193    var $Timeout      = 10;
194
195    /**
196     *  Sets SMTP class debugging on or off.
197     *  @var bool
198     */
199    var $SMTPDebug    = false;
200
201    /**
202     * Prevents the SMTP connection from being closed after each mail
203     * sending.  If this is set to true then to close the connection
204     * requires an explicit call to SmtpClose().
205     * @var bool
206     */
207    var $SMTPKeepAlive = false;
208
209    /**#@+
210     * @access private
211     */
212    var $smtp            = NULL;
213    var $to              = array();
214    var $cc              = array();
215    var $bcc             = array();
216    var $ReplyTo         = array();
217    var $attachment      = array();
218    var $CustomHeader    = array();
219    var $message_type    = "";
220    var $boundary        = array();
221    var $language        = array();
222    var $error_count     = 0;
223    var $LE              = "\n";
224    /**#@-*/
225
226		/////////////////////////////////////////////////
227   	// CONSTRUCTOR
228   	/////////////////////////////////////////////////
229   	/**
230   	* Constructor, just set up language
231   	* @param none
232   	* @return void
233   	*/
234   	function PHPMailer() {
235		global $conf;
236		global $charset;
237
238		$this->CharSet = $charset;
239
240   		$this->SetLanguage();
241
242		$this->Mailer = $conf['app']['emailType'];
243
244		if ($this->Mailer == 'smtp') {									// Set smtp variables
245			$this->Host = $conf['app']['smtpHost'];
246			$this->Port = $conf['app']['smtpPort'];
247		}
248
249		if ($this->Mailer == 'sendmail')								// Set sendmail variables
250			$this->Sendmail = $conf['app']['sendmailPath'];
251
252		if ($this->Mailer == 'qmail')									// Set qmail variables
253			$this->Sendmail = $conf['app']['qmailPath'];
254   	}
255
256    /////////////////////////////////////////////////
257    // VARIABLE METHODS
258    /////////////////////////////////////////////////
259
260    /**
261     * Sets message type to HTML.
262     * @param bool $bool
263     * @return void
264     */
265    function IsHTML($bool) {
266        if($bool == true)
267            $this->ContentType = "text/html";
268        else
269            $this->ContentType = "text/plain";
270    }
271
272    /**
273     * Sets Mailer to send message using SMTP.
274     * @return void
275     */
276    function IsSMTP() {
277        $this->Mailer = "smtp";
278    }
279
280    /**
281     * Sets Mailer to send message using PHP mail() function.
282     * @return void
283     */
284    function IsMail() {
285        $this->Mailer = "mail";
286    }
287
288    /**
289     * Sets Mailer to send message using the $Sendmail program.
290     * @return void
291     */
292    function IsSendmail() {
293        $this->Mailer = "sendmail";
294    }
295
296    /**
297     * Sets Mailer to send message using the qmail MTA.
298     * @return void
299     */
300    function IsQmail() {
301        $this->Sendmail = "/var/qmail/bin/sendmail";
302        $this->Mailer = "sendmail";
303    }
304
305
306    /////////////////////////////////////////////////
307    // RECIPIENT METHODS
308    /////////////////////////////////////////////////
309
310    /**
311     * Adds a "To" address.
312     * @param string $address
313     * @param string $name
314     * @return void
315     */
316    function AddAddress($address, $name = "") {
317        $cur = count($this->to);
318        $this->to[$cur][0] = trim($address);
319        $this->to[$cur][1] = $name;
320    }
321
322    /**
323     * Adds a "Cc" address. Note: this function works
324     * with the SMTP mailer on win32, not with the "mail"
325     * mailer.
326     * @param string $address
327     * @param string $name
328     * @return void
329    */
330    function AddCC($address, $name = "") {
331        $cur = count($this->cc);
332        $this->cc[$cur][0] = trim($address);
333        $this->cc[$cur][1] = $name;
334    }
335
336    /**
337     * Adds a "Bcc" address. Note: this function works
338     * with the SMTP mailer on win32, not with the "mail"
339     * mailer.
340     * @param string $address
341     * @param string $name
342     * @return void
343     */
344    function AddBCC($address, $name = "") {
345        $cur = count($this->bcc);
346        $this->bcc[$cur][0] = trim($address);
347        $this->bcc[$cur][1] = $name;
348    }
349
350    /**
351     * Adds a "Reply-to" address.
352     * @param string $address
353     * @param string $name
354     * @return void
355     */
356    function AddReplyTo($address, $name = "") {
357        $cur = count($this->ReplyTo);
358        $this->ReplyTo[$cur][0] = trim($address);
359        $this->ReplyTo[$cur][1] = $name;
360    }
361
362
363    /////////////////////////////////////////////////
364    // MAIL SENDING METHODS
365    /////////////////////////////////////////////////
366
367    /**
368     * Creates message and assigns Mailer. If the message is
369     * not sent successfully then it returns false.  Use the ErrorInfo
370     * variable to view description of the error.
371     * @return bool
372     */
373    function Send() {
374		$header = "";
375        $body = "";
376        $result = true;
377
378        if((count($this->to) + count($this->cc) + count($this->bcc)) < 1)
379        {
380            $this->SetError($this->Lang("provide_address"));
381            return false;
382        }
383
384        // Set whether the message is multipart/alternative
385        if(!empty($this->AltBody))
386            $this->ContentType = "multipart/alternative";
387
388        $this->error_count = 0; // reset errors
389        $this->SetMessageType();
390        $header .= $this->CreateHeader();
391        $body = $this->CreateBody();
392
393		// Nick Korbel - 08-21-2005
394		if (version_compare('4.3.0',phpversion(), '<=') == 1) {
395			$this->Subject = html_entity_decode($this->Subject, ENT_COMPAT, $this->CharSet);
396			if ($this->ContentType != "text/html")
397				$body = html_entity_decode($body, ENT_COMPAT, $this->CharSet);
398		}
399
400        if($body == "") { return false; }
401
402        // Choose the mailer
403        switch($this->Mailer)
404        {
405            case "sendmail":
406                $result = $this->SendmailSend($header, $body);
407                break;
408            case "mail":
409                $result = $this->MailSend($header, $body);
410                break;
411            case "smtp":
412                $result = $this->SmtpSend($header, $body);
413                break;
414            default:
415            $this->SetError($this->Mailer . $this->Lang("mailer_not_supported"));
416                $result = false;
417                break;
418        }
419
420        return $result;
421    }
422
423    /**
424     * Sends mail using the $Sendmail program.
425     * @access private
426     * @return bool
427     */
428    function SendmailSend($header, $body) {
429        if ($this->Sender != "")
430            $sendmail = sprintf("%s -oi -f %s -t", $this->Sendmail, $this->Sender);
431        else
432            $sendmail = sprintf("%s -oi -t", $this->Sendmail);
433
434        if(!@$mail = popen($sendmail, "w"))
435        {
436            $this->SetError($this->Lang("execute") . $this->Sendmail);
437            return false;
438        }
439
440        fputs($mail, $header);
441        fputs($mail, $body);
442
443        $result = pclose($mail) >> 8 & 0xFF;
444        if($result != 0)
445        {
446            $this->SetError($this->Lang("execute") . $this->Sendmail);
447            return false;
448        }
449
450        return true;
451    }
452
453    /**
454     * Sends mail using the PHP mail() function.
455     * @access private
456     * @return bool
457     */
458    function MailSend($header, $body) {
459        $to = "";
460        for($i = 0; $i < count($this->to); $i++)
461        {
462            if($i != 0) { $to .= ", "; }
463            $to .= $this->to[$i][0];
464        }
465
466        if ($this->Sender != "" && strlen(ini_get("safe_mode"))< 1)
467        {
468            $old_from = ini_get("sendmail_from");
469            ini_set("sendmail_from", $this->Sender);
470            $params = sprintf("-oi -f %s", $this->Sender);
471            $rt = @mail($to, $this->EncodeHeader($this->Subject), $body,
472                        $header, $params);
473        }
474        else
475            $rt = @mail($to, $this->EncodeHeader($this->Subject), $body, $header);
476
477        if (isset($old_from))
478            ini_set("sendmail_from", $old_from);
479
480        if(!$rt)
481        {
482            $this->SetError($this->Lang("instantiate"));
483            return false;
484        }
485
486        return true;
487    }
488
489    /**
490     * Sends mail via SMTP using PhpSMTP (Author:
491     * Chris Ryan).  Returns bool.  Returns false if there is a
492     * bad MAIL FROM, RCPT, or DATA input.
493     * @access private
494     * @return bool
495     */
496    function SmtpSend($header, $body) {
497        include_once($this->PluginDir . "Smtp.class.php");
498        $error = "";
499        $bad_rcpt = array();
500
501        if(!$this->SmtpConnect())
502            return false;
503
504        $smtp_from = ($this->Sender == "") ? $this->From : $this->Sender;
505        if(!$this->smtp->Mail($smtp_from))
506        {
507            $error = $this->Lang("from_failed") . $smtp_from;
508            $this->SetError($error);
509            $this->smtp->Reset();
510            return false;
511        }
512
513        // Attempt to send attach all recipients
514        for($i = 0; $i < count($this->to); $i++)
515        {
516            if(!$this->smtp->Recipient($this->to[$i][0]))
517                $bad_rcpt[] = $this->to[$i][0];
518        }
519        for($i = 0; $i < count($this->cc); $i++)
520        {
521            if(!$this->smtp->Recipient($this->cc[$i][0]))
522                $bad_rcpt[] = $this->cc[$i][0];
523        }
524        for($i = 0; $i < count($this->bcc); $i++)
525        {
526            if(!$this->smtp->Recipient($this->bcc[$i][0]))
527                $bad_rcpt[] = $this->bcc[$i][0];
528        }
529
530        if(count($bad_rcpt) > 0) // Create error message
531        {
532            for($i = 0; $i < count($bad_rcpt); $i++)
533            {
534                if($i != 0) { $error .= ", "; }
535                $error .= $bad_rcpt[$i];
536            }
537            $error = $this->Lang("recipients_failed") . $error;
538            $this->SetError($error);
539            $this->smtp->Reset();
540            return false;
541        }
542
543        if(!$this->smtp->Data($header . $body))
544        {
545            $this->SetError($this->Lang("data_not_accepted"));
546            $this->smtp->Reset();
547            return false;
548        }
549        if($this->SMTPKeepAlive == true)
550            $this->smtp->Reset();
551        else
552            $this->SmtpClose();
553
554        return true;
555    }
556
557    /**
558     * Initiates a connection to an SMTP server.  Returns false if the
559     * operation failed.
560     * @access private
561     * @return bool
562     */
563    function SmtpConnect() {
564        if($this->smtp == NULL) { $this->smtp = new SMTP(); }
565
566        $this->smtp->do_debug = $this->SMTPDebug;
567        $hosts = explode(";", $this->Host);
568        $index = 0;
569        $connection = ($this->smtp->Connected());
570
571        // Retry while there is no connection
572        while($index < count($hosts) && $connection == false)
573        {
574            if(strstr($hosts[$index], ":"))
575                list($host, $port) = explode(":", $hosts[$index]);
576            else
577            {
578                $host = $hosts[$index];
579                $port = $this->Port;
580            }
581
582            if($this->smtp->Connect($host, $port, $this->Timeout))
583            {
584                if ($this->Helo != '')
585                    $this->smtp->Hello($this->Helo);
586                else
587                    $this->smtp->Hello($this->ServerHostname());
588
589                if($this->SMTPAuth)
590                {
591                    if(!$this->smtp->Authenticate($this->Username,
592                                                  $this->Password))
593                    {
594                        $this->SetError($this->Lang("authenticate"));
595                        $this->smtp->Reset();
596                        $connection = false;
597                    }
598                }
599                $connection = true;
600            }
601            $index++;
602        }
603        if(!$connection)
604            $this->SetError($this->Lang("connect_host"));
605
606        return $connection;
607    }
608
609    /**
610     * Closes the active SMTP session if one exists.
611     * @return void
612     */
613    function SmtpClose() {
614        if($this->smtp != NULL)
615        {
616            if($this->smtp->Connected())
617            {
618                $this->smtp->Quit();
619                $this->smtp->Close();
620            }
621        }
622    }
623
624    /**
625     * Sets the language for all class error messages. Always in English.
626     * @param none
627     * @access public
628     * @return bool
629     */
630    function SetLanguage() {
631        /**
632    	* Only printing errors in english
633    	*/
634    	$PHPMAILER_LANG = array();
635
636		$PHPMAILER_LANG["provide_address"] = 'You must provide at least one ' .
637		                                     'recipient email address.';
638		$PHPMAILER_LANG["mailer_not_supported"] = ' mailer is not supported.';
639		$PHPMAILER_LANG["execute"] = 'Could not execute: ';
640		$PHPMAILER_LANG["instantiate"] = 'Could not instantiate mail function.';
641		$PHPMAILER_LANG["authenticate"] = 'SMTP Error: Could not authenticate.';
642		$PHPMAILER_LANG["from_failed"] = 'The following From address failed: ';
643		$PHPMAILER_LANG["recipients_failed"] = 'SMTP Error: The following ' .
644		                                       'recipients failed: ';
645		$PHPMAILER_LANG["data_not_accepted"] = 'SMTP Error: Data not accepted.';
646		$PHPMAILER_LANG["connect_host"] = 'SMTP Error: Could not connect to SMTP host.';
647		$PHPMAILER_LANG["file_access"] = 'Could not access file: ';
648		$PHPMAILER_LANG["file_open"] = 'File Error: Could not open file: ';
649		$PHPMAILER_LANG["encoding"] = 'Unknown encoding: ';
650
651        $this->language = $PHPMAILER_LANG;
652
653        return true;
654    }
655
656    /////////////////////////////////////////////////
657    // MESSAGE CREATION METHODS
658    /////////////////////////////////////////////////
659
660    /**
661     * Creates recipient headers.
662     * @access private
663     * @return string
664     */
665    function AddrAppend($type, $addr) {
666        $addr_str = $type . ": ";
667        $addr_str .= $this->AddrFormat($addr[0]);
668        if(count($addr) > 1)
669        {
670            for($i = 1; $i < count($addr); $i++)
671                $addr_str .= ", " . $this->AddrFormat($addr[$i]);
672        }
673        $addr_str .= $this->LE;
674
675        return $addr_str;
676    }
677
678    /**
679     * Formats an address correctly.
680     * @access private
681     * @return string
682     */
683    function AddrFormat($addr) {
684        if(empty($addr[1]))
685            $formatted = $addr[0];
686        else
687        {
688            $formatted = $this->EncodeHeader($addr[1], 'phrase') . " <" .
689                         $addr[0] . ">";
690        }
691
692        return $formatted;
693    }
694
695    /**
696     * Wraps message for use with mailers that do not
697     * automatically perform wrapping and for quoted-printable.
698     * Original written by philippe.
699     * @access private
700     * @return string
701     */
702    function WrapText($message, $length, $qp_mode = false) {
703        $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE;
704
705        $message = $this->FixEOL($message);
706        if (substr($message, -1) == $this->LE)
707            $message = substr($message, 0, -1);
708
709        $line = explode($this->LE, $message);
710        $message = "";
711        for ($i=0 ;$i < count($line); $i++)
712        {
713          $line_part = explode(" ", $line[$i]);
714          $buf = "";
715          for ($e = 0; $e<count($line_part); $e++)
716          {
717              $word = $line_part[$e];
718              if ($qp_mode and (strlen($word) > $length))
719              {
720                $space_left = $length - strlen($buf) - 1;
721                if ($e != 0)
722                {
723                    if ($space_left > 20)
724                    {
725                        $len = $space_left;
726                        if (substr($word, $len - 1, 1) == "=")
727                          $len--;
728                        elseif (substr($word, $len - 2, 1) == "=")
729                          $len -= 2;
730                        $part = substr($word, 0, $len);
731                        $word = substr($word, $len);
732                        $buf .= " " . $part;
733                        $message .= $buf . sprintf("=%s", $this->LE);
734                    }
735                    else
736                    {
737                        $message .= $buf . $soft_break;
738                    }
739                    $buf = "";
740                }
741                while (strlen($word) > 0)
742                {
743                    $len = $length;
744                    if (substr($word, $len - 1, 1) == "=")
745                        $len--;
746                    elseif (substr($word, $len - 2, 1) == "=")
747                        $len -= 2;
748                    $part = substr($word, 0, $len);
749                    $word = substr($word, $len);
750
751                    if (strlen($word) > 0)
752                        $message .= $part . sprintf("=%s", $this->LE);
753                    else
754                        $buf = $part;
755                }
756              }
757              else
758              {
759                $buf_o = $buf;
760                $buf .= ($e == 0) ? $word : (" " . $word);
761
762                if (strlen($buf) > $length and $buf_o != "")
763                {
764                    $message .= $buf_o . $soft_break;
765                    $buf = $word;
766                }
767              }
768          }
769          $message .= $buf . $this->LE;
770        }
771
772        return $message;
773    }
774
775    /**
776     * Set the body wrapping.
777     * @access private
778     * @return void
779     */
780    function SetWordWrap() {
781        if($this->WordWrap < 1)
782            return;
783
784        switch($this->message_type)
785        {
786           case "alt":
787              // fall through
788           case "alt_attachment":
789              $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap);
790              break;
791           default:
792              $this->Body = $this->WrapText($this->Body, $this->WordWrap);
793              break;
794        }
795    }
796
797    /**
798     * Assembles message header.
799     * @access private
800     * @return string
801     */
802    function CreateHeader() {
803        $result = "";
804
805        // Set the boundaries
806        $uniq_id = md5(uniqid(time()));
807        $this->boundary[1] = "b1_" . $uniq_id;
808        $this->boundary[2] = "b2_" . $uniq_id;
809
810        $result .= $this->HeaderLine("Date", $this->RFCDate());
811        if($this->Sender == "")
812            $result .= $this->HeaderLine("Return-Path", trim($this->From));
813        else
814            $result .= $this->HeaderLine("Return-Path", trim($this->Sender));
815
816        // To be created automatically by mail()
817        if($this->Mailer != "mail")
818        {
819            if(count($this->to) > 0)
820                $result .= $this->AddrAppend("To", $this->to);
821            else if (count($this->cc) == 0)
822                $result .= $this->HeaderLine("To", "undisclosed-recipients:;");
823            if(count($this->cc) > 0)
824                $result .= $this->AddrAppend("Cc", $this->cc);
825        }
826
827        $from = array();
828        $from[0][0] = trim($this->From);
829        $from[0][1] = $this->FromName;
830        $result .= $this->AddrAppend("From", $from);
831
832        // sendmail and mail() extract Bcc from the header before sending
833        if((($this->Mailer == "sendmail") || ($this->Mailer == "mail")) && (count($this->bcc) > 0))
834            $result .= $this->AddrAppend("Bcc", $this->bcc);
835
836        if(count($this->ReplyTo) > 0)
837            $result .= $this->AddrAppend("Reply-to", $this->ReplyTo);
838
839        // mail() sets the subject itself
840        if($this->Mailer != "mail")
841            $result .= $this->HeaderLine("Subject", $this->EncodeHeader(trim($this->Subject)));
842
843        $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE);
844        $result .= $this->HeaderLine("X-Priority", $this->Priority);
845        $result .= $this->HeaderLine("X-Mailer", "PHPMailer [version " . $this->Version . "]");
846
847        if($this->ConfirmReadingTo != "")
848        {
849            $result .= $this->HeaderLine("Disposition-Notification-To",
850                       "<" . trim($this->ConfirmReadingTo) . ">");
851        }
852
853        // Add custom headers
854        for($index = 0; $index < count($this->CustomHeader); $index++)
855        {
856            $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]),
857                       $this->EncodeHeader(trim($this->CustomHeader[$index][1])));
858        }
859        $result .= $this->HeaderLine("MIME-Version", "1.0");
860
861        switch($this->message_type)
862        {
863            case "plain":
864                $result .= $this->HeaderLine("Content-Transfer-Encoding", $this->Encoding);
865                $result .= sprintf("Content-Type: %s; charset=\"%s\"",
866                                    $this->ContentType, $this->CharSet);
867                break;
868            case "attachments":
869                // fall through
870            case "alt_attachments":
871                if($this->InlineImageExists())
872                {
873                    $result .= sprintf("Content-Type: %s;%s\ttype=\"text/html\";%s\tboundary=\"%s\"%s",
874                                    "multipart/related", $this->LE, $this->LE,
875                                    $this->boundary[1], $this->LE);
876                }
877                else
878                {
879                    $result .= $this->HeaderLine("Content-Type", "multipart/mixed;");
880                    $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"');
881                }
882                break;
883            case "alt":
884                $result .= $this->HeaderLine("Content-Type", "multipart/alternative;");
885                $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"');
886                break;
887        }
888
889        if($this->Mailer != "mail")
890            $result .= $this->LE.$this->LE;
891
892        return $result;
893    }
894
895    /**
896     * Assembles the message body.  Returns an empty string on failure.
897     * @access private
898     * @return string
899     */
900    function CreateBody() {
901        $result = "";
902
903        $this->SetWordWrap();
904
905        switch($this->message_type)
906        {
907            case "alt":
908                $result .= $this->GetBoundary($this->boundary[1], "",
909                                              "text/plain", "");
910                $result .= $this->EncodeString($this->AltBody, $this->Encoding);
911                $result .= $this->LE.$this->LE;
912                $result .= $this->GetBoundary($this->boundary[1], "",
913                                              "text/html", "");
914
915                $result .= $this->EncodeString($this->Body, $this->Encoding);
916                $result .= $this->LE.$this->LE;
917
918                $result .= $this->EndBoundary($this->boundary[1]);
919                break;
920            case "plain":
921                $result .= $this->EncodeString($this->Body, $this->Encoding);
922                break;
923            case "attachments":
924                $result .= $this->GetBoundary($this->boundary[1], "", "", "");
925                $result .= $this->EncodeString($this->Body, $this->Encoding);
926                $result .= $this->LE;
927
928                $result .= $this->AttachAll();
929                break;
930            case "alt_attachments":
931                $result .= sprintf("--%s%s", $this->boundary[1], $this->LE);
932                $result .= sprintf("Content-Type: %s;%s" .
933                                   "\tboundary=\"%s\"%s",
934                                   "multipart/alternative", $this->LE,
935                                   $this->boundary[2], $this->LE.$this->LE);
936
937                // Create text body
938                $result .= $this->GetBoundary($this->boundary[2], "",
939                                              "text/plain", "") . $this->LE;
940
941                $result .= $this->EncodeString($this->AltBody, $this->Encoding);
942                $result .= $this->LE.$this->LE;
943
944                // Create the HTML body
945                $result .= $this->GetBoundary($this->boundary[2], "",
946                                              "text/html", "") . $this->LE;
947
948                $result .= $this->EncodeString($this->Body, $this->Encoding);
949                $result .= $this->LE.$this->LE;
950
951                $result .= $this->EndBoundary($this->boundary[2]);
952
953                $result .= $this->AttachAll();
954                break;
955        }
956        if($this->IsError())
957            $result = "";
958
959        return $result;
960    }
961
962    /**
963     * Returns the start of a message boundary.
964     * @access private
965     */
966    function GetBoundary($boundary, $charSet, $contentType, $encoding) {
967        $result = "";
968        if($charSet == "") { $charSet = $this->CharSet; }
969        if($contentType == "") { $contentType = $this->ContentType; }
970        if($encoding == "") { $encoding = $this->Encoding; }
971
972        $result .= $this->TextLine("--" . $boundary);
973        $result .= sprintf("Content-Type: %s; charset = \"%s\"",
974                            $contentType, $charSet);
975        $result .= $this->LE;
976        $result .= $this->HeaderLine("Content-Transfer-Encoding", $encoding);
977        $result .= $this->LE;
978
979        return $result;
980    }
981
982    /**
983     * Returns the end of a message boundary.
984     * @access private
985     */
986    function EndBoundary($boundary) {
987        return $this->LE . "--" . $boundary . "--" . $this->LE;
988    }
989
990    /**
991     * Sets the message type.
992     * @access private
993     * @return void
994     */
995    function SetMessageType() {
996        if(count($this->attachment) < 1 && strlen($this->AltBody) < 1)
997            $this->message_type = "plain";
998        else
999        {
1000            if(count($this->attachment) > 0)
1001                $this->message_type = "attachments";
1002            if(strlen($this->AltBody) > 0 && count($this->attachment) < 1)
1003                $this->message_type = "alt";
1004            if(strlen($this->AltBody) > 0 && count($this->attachment) > 0)
1005                $this->message_type = "alt_attachments";
1006        }
1007    }
1008
1009    /**
1010     * Returns a formatted header line.
1011     * @access private
1012     * @return string
1013     */
1014    function HeaderLine($name, $value) {
1015        return $name . ": " . $value . $this->LE;
1016    }
1017
1018    /**
1019     * Returns a formatted mail line.
1020     * @access private
1021     * @return string
1022     */
1023    function TextLine($value) {
1024        return $value . $this->LE;
1025    }
1026
1027    /////////////////////////////////////////////////
1028    // ATTACHMENT METHODS
1029    /////////////////////////////////////////////////
1030
1031    /**
1032     * Adds an attachment from a path on the filesystem.
1033     * Returns false if the file could not be found
1034     * or accessed.
1035     * @param string $path Path to the attachment.
1036     * @param string $name Overrides the attachment name.
1037     * @param string $encoding File encoding (see $Encoding).
1038     * @param string $type File extension (MIME) type.
1039     * @return bool
1040     */
1041    function AddAttachment($path, $name = "", $encoding = "base64",
1042                           $type = "application/octet-stream") {
1043        if(!@is_file($path))
1044        {
1045            $this->SetError($this->Lang("file_access") . $path);
1046            return false;
1047        }
1048
1049        $filename = basename($path);
1050        if($name == "")
1051            $name = $filename;
1052
1053        $cur = count($this->attachment);
1054        $this->attachment[$cur][0] = $path;
1055        $this->attachment[$cur][1] = $filename;
1056        $this->attachment[$cur][2] = $name;
1057        $this->attachment[$cur][3] = $encoding;
1058        $this->attachment[$cur][4] = $type;
1059        $this->attachment[$cur][5] = false; // isStringAttachment
1060        $this->attachment[$cur][6] = "attachment";
1061        $this->attachment[$cur][7] = 0;
1062
1063        return true;
1064    }
1065
1066    /**
1067     * Attaches all fs, string, and binary attachments to the message.
1068     * Returns an empty string on failure.
1069     * @access private
1070     * @return string
1071     */
1072    function AttachAll() {
1073        // Return text of body
1074        $mime = array();
1075
1076        // Add all attachments
1077        for($i = 0; $i < count($this->attachment); $i++)
1078        {
1079            // Check for string attachment
1080            $bString = $this->attachment[$i][5];
1081            if ($bString)
1082                $string = $this->attachment[$i][0];
1083            else
1084                $path = $this->attachment[$i][0];
1085
1086            $filename    = $this->attachment[$i][1];
1087            $name        = $this->attachment[$i][2];
1088            $encoding    = $this->attachment[$i][3];
1089            $type        = $this->attachment[$i][4];
1090            $disposition = $this->attachment[$i][6];
1091            $cid         = $this->attachment[$i][7];
1092
1093            $mime[] = sprintf("--%s%s", $this->boundary[1], $this->LE);
1094            $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $name, $this->LE);
1095            $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE);
1096
1097            if($disposition == "inline")
1098                $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE);
1099
1100            $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s",
1101                              $disposition, $name, $this->LE.$this->LE);
1102
1103            // Encode as string attachment
1104            if($bString)
1105            {
1106                $mime[] = $this->EncodeString($string, $encoding);
1107                if($this->IsError()) { return ""; }
1108                $mime[] = $this->LE.$this->LE;
1109            }
1110            else
1111            {
1112                $mime[] = $this->EncodeFile($path, $encoding);
1113                if($this->IsError()) { return ""; }
1114                $mime[] = $this->LE.$this->LE;
1115            }
1116        }
1117
1118        $mime[] = sprintf("--%s--%s", $this->boundary[1], $this->LE);
1119
1120        return join("", $mime);
1121    }
1122
1123    /**
1124     * Encodes attachment in requested format.  Returns an
1125     * empty string on failure.
1126     * @access private
1127     * @return string
1128     */
1129    function EncodeFile ($path, $encoding = "base64") {
1130        if(!@$fd = fopen($path, "rb"))
1131        {
1132            $this->SetError($this->Lang("file_open") . $path);
1133            return "";
1134        }
1135        $file_buffer = fread($fd, filesize($path));
1136        $file_buffer = $this->EncodeString($file_buffer, $encoding);
1137        fclose($fd);
1138
1139        return $file_buffer;
1140    }
1141
1142    /**
1143     * Encodes string to requested format. Returns an
1144     * empty string on failure.
1145     * @access private
1146     * @return string
1147     */
1148    function EncodeString ($str, $encoding = "base64") {
1149        $encoded = "";
1150        switch(strtolower($encoding)) {
1151          case "base64":
1152              // chunk_split is found in PHP >= 3.0.6
1153              $encoded = chunk_split(base64_encode($str), 76, $this->LE);
1154              break;
1155          case "7bit":
1156          case "8bit":
1157              $encoded = $this->FixEOL($str);
1158              if (substr($encoded, -(strlen($this->LE))) != $this->LE)
1159                $encoded .= $this->LE;
1160              break;
1161          case "binary":
1162              $encoded = $str;
1163              break;
1164          case "quoted-printable":
1165              $encoded = $this->EncodeQP($str);
1166              break;
1167          default:
1168              $this->SetError($this->Lang("encoding") . $encoding);
1169              break;
1170        }
1171        return $encoded;
1172    }
1173
1174    /**
1175     * Encode a header string to best of Q, B, quoted or none.
1176     * @access private
1177     * @return string
1178     */
1179    function EncodeHeader ($str, $position = 'text') {
1180      $x = 0;
1181
1182      switch (strtolower($position)) {
1183        case 'phrase':
1184          if (!preg_match('/[\200-\377]/', $str)) {
1185            // Can't use addslashes as we don't know what value has magic_quotes_sybase.
1186            $encoded = addcslashes($str, "\0..\37\177\\\"");
1187
1188            if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str))
1189              return ($encoded);
1190            else
1191              return ("\"$encoded\"");
1192          }
1193          $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
1194          break;
1195        case 'comment':
1196          $x = preg_match_all('/[()"]/', $str, $matches);
1197          // Fall-through
1198        case 'text':
1199        default:
1200          $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
1201          break;
1202      }
1203
1204      if ($x == 0)
1205        return ($str);
1206
1207      $maxlen = 75 - 7 - strlen($this->CharSet);
1208      // Try to select the encoding which should produce the shortest output
1209      if (strlen($str)/3 < $x) {
1210        $encoding = 'B';
1211        $encoded = base64_encode($str);
1212        $maxlen -= $maxlen % 4;
1213        $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
1214      } else {
1215        $encoding = 'Q';
1216        $encoded = $this->EncodeQ($str, $position);
1217        $encoded = $this->WrapText($encoded, $maxlen, true);
1218        $encoded = str_replace("=".$this->LE, "\n", trim($encoded));
1219      }
1220
1221      $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded);
1222      $encoded = trim(str_replace("\n", $this->LE, $encoded));
1223
1224      return $encoded;
1225    }
1226
1227    /**
1228     * Encode string to quoted-printable.
1229     * @access private
1230     * @return string
1231     */
1232    function EncodeQP ($str) {
1233        $encoded = $this->FixEOL($str);
1234        if (substr($encoded, -(strlen($this->LE))) != $this->LE)
1235            $encoded .= $this->LE;
1236
1237        // Replace every high ascii, control and = characters
1238        $encoded = preg_replace('/([\000-\010\013\014\016-\037\075\177-\377])/e',
1239                  "'='.sprintf('%02X', ord('\\1'))", $encoded);
1240        // Replace every spaces and tabs when it's the last character on a line
1241        $encoded = preg_replace("/([\011\040])".$this->LE."/e",
1242                  "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded);
1243
1244        // Maximum line length of 76 characters before CRLF (74 + space + '=')
1245        $encoded = $this->WrapText($encoded, 74, true);
1246
1247        return $encoded;
1248    }
1249
1250    /**
1251     * Encode string to q encoding.
1252     * @access private
1253     * @return string
1254     */
1255    function EncodeQ ($str, $position = "text") {
1256        // There should not be any EOL in the string
1257        $encoded = preg_replace("[\r\n]", "", $str);
1258
1259        switch (strtolower($position)) {
1260          case "phrase":
1261            $encoded = preg_replace("/([^A-Za-z0-9!*+\/ -])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded);
1262            break;
1263          case "comment":
1264            $encoded = preg_replace("/([\(\)\"])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded);
1265          case "text":
1266          default:
1267            // Replace every high ascii, control =, ? and _ characters
1268            $encoded = preg_replace('/([\000-\011\013\014\016-\037\075\077\137\177-\377])/e',
1269                  "'='.sprintf('%02X', ord('\\1'))", $encoded);
1270            break;
1271        }
1272
1273        // Replace every spaces to _ (more readable than =20)
1274        $encoded = str_replace(" ", "_", $encoded);
1275
1276        return $encoded;
1277    }
1278
1279    /**
1280     * Adds a string or binary attachment (non-filesystem) to the list.
1281     * This method can be used to attach ascii or binary data,
1282     * such as a BLOB record from a database.
1283     * @param string $string String attachment data.
1284     * @param string $filename Name of the attachment.
1285     * @param string $encoding File encoding (see $Encoding).
1286     * @param string $type File extension (MIME) type.
1287     * @return void
1288     */
1289    function AddStringAttachment($string, $filename, $encoding = "base64",
1290                                 $type = "application/octet-stream") {
1291        // Append to $attachment array
1292        $cur = count($this->attachment);
1293        $this->attachment[$cur][0] = $string;
1294        $this->attachment[$cur][1] = $filename;
1295        $this->attachment[$cur][2] = $filename;
1296        $this->attachment[$cur][3] = $encoding;
1297        $this->attachment[$cur][4] = $type;
1298        $this->attachment[$cur][5] = true; // isString
1299        $this->attachment[$cur][6] = "attachment";
1300        $this->attachment[$cur][7] = 0;
1301    }
1302
1303    /**
1304     * Adds an embedded attachment.  This can include images, sounds, and
1305     * just about any other document.  Make sure to set the $type to an
1306     * image type.  For JPEG images use "image/jpeg" and for GIF images
1307     * use "image/gif".
1308     * @param string $path Path to the attachment.
1309     * @param string $cid Content ID of the attachment.  Use this to identify
1310     *        the Id for accessing the image in an HTML form.
1311     * @param string $name Overrides the attachment name.
1312     * @param string $encoding File encoding (see $Encoding).
1313     * @param string $type File extension (MIME) type.
1314     * @return bool
1315     */
1316    function AddEmbeddedImage($path, $cid, $name = "", $encoding = "base64",
1317                              $type = "application/octet-stream") {
1318
1319        if(!@is_file($path))
1320        {
1321            $this->SetError($this->Lang("file_access") . $path);
1322            return false;
1323        }
1324
1325        $filename = basename($path);
1326        if($name == "")
1327            $name = $filename;
1328
1329        // Append to $attachment array
1330        $cur = count($this->attachment);
1331        $this->attachment[$cur][0] = $path;
1332        $this->attachment[$cur][1] = $filename;
1333        $this->attachment[$cur][2] = $name;
1334        $this->attachment[$cur][3] = $encoding;
1335        $this->attachment[$cur][4] = $type;
1336        $this->attachment[$cur][5] = false; // isStringAttachment
1337        $this->attachment[$cur][6] = "inline";
1338        $this->attachment[$cur][7] = $cid;
1339
1340        return true;
1341    }
1342
1343    /**
1344     * Returns true if an inline attachment is present.
1345     * @access private
1346     * @return bool
1347     */
1348    function InlineImageExists() {
1349        $result = false;
1350        for($i = 0; $i < count($this->attachment); $i++)
1351        {
1352            if($this->attachment[$i][6] == "inline")
1353            {
1354                $result = true;
1355                break;
1356            }
1357        }
1358
1359        return $result;
1360    }
1361
1362    /////////////////////////////////////////////////
1363    // MESSAGE RESET METHODS
1364    /////////////////////////////////////////////////
1365
1366    /**
1367     * Clears all recipients assigned in the TO array.  Returns void.
1368     * @return void
1369     */
1370    function ClearAddresses() {
1371        $this->to = array();
1372    }
1373
1374    /**
1375     * Clears all recipients assigned in the CC array.  Returns void.
1376     * @return void
1377     */
1378    function ClearCCs() {
1379        $this->cc = array();
1380    }
1381
1382    /**
1383     * Clears all recipients assigned in the BCC array.  Returns void.
1384     * @return void
1385     */
1386    function ClearBCCs() {
1387        $this->bcc = array();
1388    }
1389
1390    /**
1391     * Clears all recipients assigned in the ReplyTo array.  Returns void.
1392     * @return void
1393     */
1394    function ClearReplyTos() {
1395        $this->ReplyTo = array();
1396    }
1397
1398    /**
1399     * Clears all recipients assigned in the TO, CC and BCC
1400     * array.  Returns void.
1401     * @return void
1402     */
1403    function ClearAllRecipients() {
1404        $this->to = array();
1405        $this->cc = array();
1406        $this->bcc = array();
1407    }
1408
1409    /**
1410     * Clears all previously set filesystem, string, and binary
1411     * attachments.  Returns void.
1412     * @return void
1413     */
1414    function ClearAttachments() {
1415        $this->attachment = array();
1416    }
1417
1418    /**
1419     * Clears all custom headers.  Returns void.
1420     * @return void
1421     */
1422    function ClearCustomHeaders() {
1423        $this->CustomHeader = array();
1424    }
1425
1426
1427    /////////////////////////////////////////////////
1428    // MISCELLANEOUS METHODS
1429    /////////////////////////////////////////////////
1430
1431    /**
1432     * Adds the error message to the error container.
1433     * Returns void.
1434     * @access private
1435     * @return void
1436     */
1437    function SetError($msg) {
1438        $this->error_count++;
1439        $this->ErrorInfo = $msg;
1440    }
1441
1442    /**
1443     * Returns the proper RFC 822 formatted date.
1444     * @access private
1445     * @return string
1446     */
1447    function RFCDate() {
1448        $tz = date("Z");
1449        $tzs = ($tz < 0) ? "-" : "+";
1450        $tz = abs($tz);
1451        $tz = ($tz/3600)*100 + ($tz%3600)/60;
1452        $result = sprintf("%s %s%04d", date("D, j M Y H:i:s"), $tzs, $tz);
1453
1454        return $result;
1455    }
1456
1457    /**
1458     * Returns the appropriate server variable.  Should work with both
1459     * PHP 4.1.0+ as well as older versions.  Returns an empty string
1460     * if nothing is found.
1461     * @access private
1462     * @return mixed
1463     */
1464    function ServerVar($varName) {
1465        global $HTTP_SERVER_VARS;
1466        global $HTTP_ENV_VARS;
1467
1468        if(!isset($_SERVER))
1469        {
1470            $_SERVER = $HTTP_SERVER_VARS;
1471            if(!isset($_SERVER["REMOTE_ADDR"]))
1472                $_SERVER = $HTTP_ENV_VARS; // must be Apache
1473        }
1474
1475        if(isset($_SERVER[$varName]))
1476            return $_SERVER[$varName];
1477        else
1478            return "";
1479    }
1480
1481    /**
1482     * Returns the server hostname or 'localhost.localdomain' if unknown.
1483     * @access private
1484     * @return string
1485     */
1486    function ServerHostname() {
1487        if ($this->Hostname != "")
1488            $result = $this->Hostname;
1489        elseif ($this->ServerVar('SERVER_NAME') != "")
1490            $result = $this->ServerVar('SERVER_NAME');
1491        else
1492            $result = "localhost.localdomain";
1493
1494        return $result;
1495    }
1496
1497    /**
1498     * Returns a message in the appropriate language.
1499     * @access private
1500     * @return string
1501     */
1502    function Lang($key) {
1503        if(count($this->language) < 1)
1504            $this->SetLanguage("en"); // set the default language
1505
1506        if(isset($this->language[$key]))
1507            return $this->language[$key];
1508        else
1509            return "Language string failed to load: " . $key;
1510    }
1511
1512    /**
1513     * Returns true if an error occurred.
1514     * @return bool
1515     */
1516    function IsError() {
1517        return ($this->error_count > 0);
1518    }
1519
1520    /**
1521     * Changes every end of line from CR or LF to CRLF.
1522     * @access private
1523     * @return string
1524     */
1525    function FixEOL($str) {
1526        $str = str_replace("\r\n", "\n", $str);
1527        $str = str_replace("\r", "\n", $str);
1528        $str = str_replace("\n", $this->LE, $str);
1529        return $str;
1530    }
1531
1532    /**
1533     * Adds a custom header.
1534     * @return void
1535     */
1536    function AddCustomHeader($custom_header) {
1537        $this->CustomHeader[] = explode(":", $custom_header, 2);
1538    }
1539}
1540
1541?>
1542