These two files need to be swapped
[isso.git] / Mail.php
1 <?php
2 /*=====================================================================*\
3 || ###################################################################
4 || # Blue Static ISSO Framework
5 || # Copyright (c)2005-2008 Blue Static
6 || #
7 || # This program is free software; you can redistribute it and/or modify
8 || # it under the terms of the GNU General Public License as published by
9 || # the Free Software Foundation; version 2 of the License.
10 || #
11 || # This program is distributed in the hope that it will be useful, but
12 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 || # more details.
15 || #
16 || # You should have received a copy of the GNU General Public License along
17 || # with this program; if not, write to the Free Software Foundation, Inc.,
18 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 || ###################################################################
20 \*=====================================================================*/
21
22 /**
23 * Mail Sender (mail.php)
24 *
25 * @package ISSO
26 */
27
28 require_once(ISSO . '/Functions.php');
29
30 /**
31 * Mail Sender
32 *
33 * This framework is a wrapper for the PHP mail function that properly
34 * sends mail with full email headers.
35 *
36 * @author Blue Static
37 * @copyright Copyright (c)2005 - 2008, Blue Static
38 * @package ISSO
39 *
40 */
41 class BSMail
42 {
43 /**
44 * The subject of the message
45 * @var string
46 */
47 private $subject = '';
48
49 /**
50 * Body plain-text of the message
51 * @var string
52 */
53 private $bodyText = '';
54
55 /**
56 * HTML multi-part body of the message
57 * @var string
58 */
59 private $bodyHtml = '';
60
61 /**
62 * The message sender's email address
63 * @var string
64 */
65 private $from = '';
66
67 /**
68 * The message sender's display name
69 * @var string
70 */
71 private $fromName = '';
72
73 /**
74 * Additional message headers
75 * @var string
76 */
77 private $headers = '';
78
79 /**
80 * The new line delimiter used in the message
81 * @var string
82 * @access private
83 */
84 private $delim = "\n";
85
86 /**
87 * Character set used to send messages with
88 * @var string
89 * @access public
90 */
91 private $charset = 'utf-8'; // should we be using iso-8859-1 ?
92
93 /**
94 * Constructor
95 */
96 public function __construct()
97 {
98 if (!BSApp::$input instanceof BSInput)
99 {
100 throw new Exception('BSApp::$input is not an instance of BSInput');
101 }
102 }
103
104 // ###################################################################
105 /**
106 * Sets the subject
107 *
108 * @param string Subject text
109 */
110 public function setSubject($subject)
111 {
112 $this->subject = $subject;
113 }
114
115 // ###################################################################
116 /**
117 * Sets the body text (required)
118 *
119 * @param string Body text
120 */
121 public function setBodyText($body)
122 {
123 $this->bodyText = $body;
124 }
125
126 // ###################################################################
127 /**
128 * Sets the HTML body (optional)
129 *
130 * @param string Body HTML
131 */
132 public function setBodyHtml($body)
133 {
134 $this->bodyHtml = $body;
135 }
136
137 // ###################################################################
138 /**
139 * Sets the from address
140 *
141 * @param string Sending email address
142 */
143 public function setFromAddress($address)
144 {
145 $this->from = $address;
146 }
147
148 // ###################################################################
149 /**
150 * Sets the from display name
151 *
152 * @param string From name
153 */
154 public function setFromName($name)
155 {
156 $this->fromName = $name;
157 }
158
159 // ###################################################################
160 /**
161 * Sets any additional headers
162 *
163 * @param string Additional headers separated by a \n
164 */
165 public function setHeaders($headers)
166 {
167 $this->headers = $headers;
168 }
169
170 // ###################################################################
171 /**
172 * Sets the character set to send the email in
173 *
174 * @param string Charset
175 */
176 public function setCharset($charset)
177 {
178 $this->charset = $charset;
179 }
180
181 // ###################################################################
182 /**
183 * Sends an email to the specified address with the specified
184 * sender, subject, and body.
185 *
186 * @param string Email address to send to
187 * @param string Name of the recipient
188 * @param bool Send an HTML multipart (if HTML body specified)?
189 *
190 * @return bool Status of the message
191 */
192 public function send($address, $name = null, $sendHtml = false)
193 {
194 if (empty($address))
195 {
196 throw new Exception('You need to specify an email address');
197 }
198
199 // make sure we have a mailer
200 // TODO - add support for SMTP
201 if (!@ini_get('sendmail_path'))
202 {
203 BSApp::debug("email: no sendmail -> not sending");
204 return false;
205 }
206
207 // sort out the to addresses
208 $address = $this->_fetchFirstLine($address);
209 $address = trim(BSApp::$input->unsanitize($address));
210 $name = $this->_fetchFirstLine($name);
211 $name = trim(BSApp::$input->unsanitize($name));
212 $tostring = ($name == null ? $address : "\"$name\" <$address>");
213
214 // sanitize the from field
215 $from = $this->_fetchFirstLine($this->from);
216 $from = trim(BSApp::$input->unsanitize($from));
217 if (empty($from))
218 {
219 throw new Exception('You need to specify a from email address');
220 }
221
222 // sanitize the from name
223 $fromName = $this->_fetchFirstLine($this->fromName);
224 $fromName = ($fromName == '' ? $from : trim(BSApp::$input->unsanitize($fromName)));
225 $fromName = $this->_encodeHeaderValue($this->fromName);
226
227 // sanitize the subject
228 $subject = $this->_fetchFirstLine($this->subject);
229 $subject = trim(BSApp::$input->unsanitize($subject));
230 if (empty($subject))
231 {
232 throw new Exception('You need to specify a subject for the message');
233 }
234
235 // sanitize the body
236 $bodyText = BSFunctions::convert_line_breaks($this->bodyText, $this->delim);
237 $bodyText = trim(BSApp::$input->unsanitize($bodyText, true));
238 if (empty($bodyText))
239 {
240 throw new Exception('You need to specify body text before sending the message');
241 }
242
243 // attach additional headers
244 $headers = BSFunctions::convert_line_breaks($this->headers, $this->delim);
245 $headers .= ((!preg_match("#{$this->delim}$#", $headers) && $headers != '') ? "\n" : '') . "From: \"{$fromName}\" <{$from}>" . $this->delim;
246 $headers .= "Return-Path: {$from}" . $this->delim;
247 $headers .= "X-Mailer: ISSO Mail Framework" . $this->delim;
248 $headers .= "MIME-Version: 1.0" . $this->delim;
249
250 // see if we need to use mime/multipart
251 if ($sendhtml && !empty($bodyhtml) == true)
252 {
253 $boundary = 'ISSO-MULTIPART-' . BSFunctions::Rand(10);
254 $headers .= "Content-Type: multipart/alternative; boundary=\"$boundary\"" . $this->delim;
255
256 $bodyHtml = BSFunctions::convert_line_breaks($this->bodyHtml, $this->delim);
257
258 // first part of the message (plaintext)
259 $body = "--$boundary" . $this->delim;
260 $body .= "Content-Type: text/plain; charset=\"" . $this->charset . "\"" . $this->delim;
261 $body .= "Content-Transfer-Encoding: 8bit" . $this->delim . $this->delim;
262 $body .= $bodyText . $this->delim;
263
264 // add some space between the parts
265 $body .= $this->delim . $this->delim . $this->delim;
266
267 // second part (html)
268 $body .= "--$boundary" . $this->delim;
269 $body .= "Content-Type: text/html; charset=\"" . $this->charset . "\"" . $this->delim;
270 $body .= "Content-Transfer-Encoding: 8bit" . $this->delim;
271 $body .= "Content-Disposition: inline" . $this->delim . $this->delim;
272 $body .= $bodyHtml . $this->delim;
273 $body .= "--$boundary--";
274 }
275 else
276 {
277 $headers .= "Content-Type: text/plain; charset=\"" . $this->charset . "\"" . $this->delim;
278 $body = $bodyText;
279 }
280 $headers .= "Content-Transfer-Encoding: 8bit" . $this->delim;
281
282 $headers = trim($headers);
283
284 // attempt to send the mail!
285 if (mail($tostring, $subject, $body, $headers, "-f {$from}"))
286 {
287 BSApp::debug("email: sent to $address");
288 }
289 else
290 {
291 BSApp::debug("email: error sending to $address");
292 }
293 }
294
295 // ###################################################################
296 /**
297 * Fetches the first line of a string
298 *
299 * @param string A string
300 *
301 * @return string The first line of the string
302 */
303 private function _fetchFirstLine($string)
304 {
305 $string = BSFunctions::convert_line_breaks($string);
306 $broken = explode("\n", $string);
307 return $broken[0];
308 }
309
310 // ###################################################################
311 /**
312 * Encodes a header value (to name, fron name, subject, etc.) according
313 * to RFC 2047
314 *
315 * @param string The text to encode
316 *
317 * @return string Encoded text
318 */
319 function _encodeHeaderValue($text)
320 {
321 if (preg_match('#[^a-zA-Z0-9\+\-\*!/]#', $text) == 0)
322 {
323 return $text;
324 }
325
326 // perform this on non-ASCII characters; excluding _ and = because we want them to be encoded as they have
327 // different meanings in mail messages
328 $text = preg_replace('#([^a-zA-Z0-9\+\-\*!/])#e', '"=" . strtoupper(dechex(ord("\\1")))', $text);
329 $text = str_replace('=20', '_' , $text);
330
331 return '=?' . $this->charset . '?q?' . $text . '?=';
332 }
333 }
334
335 ?>