1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* MUA (Mail User Agent) implementation. |
4
|
|
|
* |
5
|
|
|
* PHP Version 5.3 |
6
|
|
|
* |
7
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public License, |
8
|
|
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can |
9
|
|
|
* obtain one at http://mozilla.org/MPL/2.0/. |
10
|
|
|
* |
11
|
|
|
* @category phpMyFAQ |
12
|
|
|
* @package Mail |
13
|
|
|
* @author Matteo Scaramuccia <[email protected]> |
14
|
|
|
* @author Thorsten Rinne <[email protected]> |
15
|
|
|
* @copyright 2009-2016 phpMyFAQ Team |
16
|
|
|
* @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 |
17
|
|
|
* @link http://www.phpmyfaq.de |
18
|
|
|
* @since 2009-09-11 |
19
|
|
|
*/ |
20
|
|
|
|
21
|
|
|
if (!defined('IS_VALID_PHPMYFAQ')) { |
22
|
|
|
exit(); |
23
|
|
|
} |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Mail |
27
|
|
|
* |
28
|
|
|
* @category phpMyFAQ |
29
|
|
|
* @package Mail |
30
|
|
|
* @author Matteo Scaramuccia <[email protected]> |
31
|
|
|
* @author Thorsten Rinne <[email protected]> |
32
|
|
|
* @copyright 2009-2016 phpMyFAQ Team |
33
|
|
|
* @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 |
34
|
|
|
* @link http://www.phpmyfaq.de |
35
|
|
|
* @since 2009-09-11 |
36
|
|
|
*/ |
37
|
|
|
class PMF_Mail |
38
|
|
|
{ |
39
|
|
|
/** |
40
|
|
|
* Type of the used MUA. Possible values: |
41
|
|
|
* - built-in. |
42
|
|
|
* |
43
|
|
|
* @var string $agent |
44
|
|
|
*/ |
45
|
|
|
public $agent; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Attached filed. |
49
|
|
|
* |
50
|
|
|
* @var mixed $attachments |
51
|
|
|
*/ |
52
|
|
|
public $attachments; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Body of the e-mail. |
56
|
|
|
* |
57
|
|
|
* @var string $body |
58
|
|
|
*/ |
59
|
|
|
public $body = ''; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Boundary. |
63
|
|
|
* |
64
|
|
|
* @var string $boundary |
65
|
|
|
*/ |
66
|
|
|
public $boundary = '----------'; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Charset. |
70
|
|
|
* |
71
|
|
|
* @var string $charset |
72
|
|
|
*/ |
73
|
|
|
public $charset = 'utf-8'; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Content disposition. |
77
|
|
|
* |
78
|
|
|
* @var string $contentDisposition |
79
|
|
|
*/ |
80
|
|
|
public $contentDisposition = 'inline'; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Content type. |
84
|
|
|
* |
85
|
|
|
* @var string $contentType |
86
|
|
|
*/ |
87
|
|
|
public $contentType = 'text/plain'; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Content transfer encoding. |
91
|
|
|
* |
92
|
|
|
* @var string $contentTransferEncoding |
93
|
|
|
*/ |
94
|
|
|
public $contentTransferEncoding = '8bit'; |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* The one and only valid End Of Line sequence as per RFC 2822: |
98
|
|
|
* carriage-return followed by line-feed. |
99
|
|
|
* |
100
|
|
|
* @var string $eol |
101
|
|
|
*/ |
102
|
|
|
public $eol = "\r\n"; |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Headers of the e-mail. |
106
|
|
|
* |
107
|
|
|
* @var string $headers |
108
|
|
|
*/ |
109
|
|
|
public $headers; |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Message of the e-mail: HTML text allowed. |
113
|
|
|
* |
114
|
|
|
* @var string $message |
115
|
|
|
*/ |
116
|
|
|
public $message; |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Alternate message of the e-mail: only plain text allowed. |
120
|
|
|
* |
121
|
|
|
* @var string $messageAlt |
122
|
|
|
*/ |
123
|
|
|
public $messageAlt; |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Message-ID of the e-mail. |
127
|
|
|
* |
128
|
|
|
* @var string $messageId |
129
|
|
|
*/ |
130
|
|
|
public $messageId; |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Priorities: 1 (Highest), 2 (High), 3 (Normal), 4 (Low), 5 (Lowest). |
134
|
|
|
* |
135
|
|
|
* @var mixed $priorities |
136
|
|
|
*/ |
137
|
|
|
public $priorities = array( |
138
|
|
|
1 => 'Highest', |
139
|
|
|
2 => 'High', |
140
|
|
|
3 => 'Normal', |
141
|
|
|
4 => 'Low', |
142
|
|
|
5 => 'Lowest'); |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Priority of the e-mail: 1 (Highest), 2 (High), 3 (Normal), 4 (Low), 5 (Lowest). |
146
|
|
|
* |
147
|
|
|
* @var int $priority |
148
|
|
|
* @see priorities |
149
|
|
|
*/ |
150
|
|
|
public $priority; |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Subject of the e-mail. |
154
|
|
|
* |
155
|
|
|
* @var string $subject |
156
|
|
|
*/ |
157
|
|
|
public $subject; |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Recipients of the e-mail as <BCC>. |
161
|
|
|
* |
162
|
|
|
* @var mixed $_bcc |
163
|
|
|
*/ |
164
|
|
|
private $_bcc; |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Recipients of the e-mail as <CC>. |
168
|
|
|
* |
169
|
|
|
* @var mixed $_cc |
170
|
|
|
*/ |
171
|
|
|
private $_cc; |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Recipients of the e-mail as <From>. |
175
|
|
|
* |
176
|
|
|
* @var mixed $_from |
177
|
|
|
*/ |
178
|
|
|
private $_from; |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Mailer string. |
182
|
|
|
* |
183
|
|
|
* @var string $_mailer |
184
|
|
|
*/ |
185
|
|
|
private $_mailer; |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Recipient of the optional notification. |
189
|
|
|
* |
190
|
|
|
* @var mixed $_notifyTo |
191
|
|
|
*/ |
192
|
|
|
private $_notifyTo; |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Recipient of the e-mail as <Reply-To>. |
196
|
|
|
* |
197
|
|
|
* @var mixed $_replyTo |
198
|
|
|
*/ |
199
|
|
|
private $_replyTo; |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Recipient of the e-mail as <Return-Path>. |
203
|
|
|
* |
204
|
|
|
* @var mixed $_returnPath |
205
|
|
|
*/ |
206
|
|
|
private $_returnPath; |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Recipient of the e-mail as <Sender>. |
210
|
|
|
* |
211
|
|
|
* @var mixed $_sender |
212
|
|
|
*/ |
213
|
|
|
private $_sender; |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Recipients of the e-mail as <TO:>. |
217
|
|
|
* |
218
|
|
|
* @var mixed $_to |
219
|
|
|
*/ |
220
|
|
|
private $_to; |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* @var PMF_Configuration |
224
|
|
|
*/ |
225
|
|
|
private $_config; |
226
|
|
|
|
227
|
|
|
/* |
228
|
|
|
* Default constructor. |
229
|
|
|
* Note: any email will be sent from the PMF administrator, use unsetFrom |
230
|
|
|
* before using setFrom. |
231
|
|
|
* |
232
|
|
|
* @param Configuration $config |
233
|
|
|
*/ |
234
|
|
|
function __construct(PMF_Configuration $config) |
235
|
|
|
{ |
236
|
|
|
// Set default value for public properties |
237
|
|
|
$this->agent = 'built-in'; |
238
|
|
|
$this->attachments = array(); |
239
|
|
|
$this->boundary = self::createBoundary(); |
240
|
|
|
$this->headers = array(); |
241
|
|
|
$this->message = ''; |
242
|
|
|
$this->messageAlt = ''; |
243
|
|
|
$this->messageId = '<'.$_SERVER['REQUEST_TIME'] . '.'. md5(microtime()) . '@' . self::getServerName() . '>'; |
244
|
|
|
$this->priority = 3; // 3 -> Normal |
245
|
|
|
$this->subject = ''; |
246
|
|
|
|
247
|
|
|
// Set default value for private properties |
248
|
|
|
$this->_config = $config; |
249
|
|
|
$this->_bcc = array(); |
250
|
|
|
$this->_cc = array(); |
251
|
|
|
$this->_from = array(); |
252
|
|
|
$this->_mailer = 'phpMyFAQ on PHP/' . PHP_VERSION; |
253
|
|
|
$this->_notifyTo = array(); |
254
|
|
|
$this->_replyTo = array(); |
255
|
|
|
$this->_returnPath = array(); |
256
|
|
|
$this->_sender = array(); |
257
|
|
|
$this->_to = array(); |
258
|
|
|
|
259
|
|
|
// Set phpMyFAQ related data |
260
|
|
|
$this->_mailer = 'phpMyFAQ/' . $this->_config->get('main.currentVersion'); |
261
|
|
|
$this->setFrom($this->_config->get('main.administrationMail')); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Add an e-mail address to an array. |
266
|
|
|
* |
267
|
|
|
* @param array $target Target array. |
268
|
|
|
* @param string $targetAlias Alias Target alias. |
269
|
|
|
* @param string $address User e-mail address. |
270
|
|
|
* @param string $name User name (optional). |
271
|
|
|
* |
272
|
|
|
* @return bool True if successful, false otherwise. |
273
|
|
|
* |
274
|
|
|
* @todo Enhance error handling using exceptions |
275
|
|
|
*/ |
276
|
|
|
private function _addEmailTo(&$target, $targetAlias, $address, $name = null) |
277
|
|
|
{ |
278
|
|
|
// Sanity check |
279
|
|
|
if (!self::validateEmail($address)) { |
280
|
|
|
trigger_error( |
281
|
|
|
"<strong>Mail Class</strong>: " . $address . " is not a valid e-mail address!", |
282
|
|
|
E_USER_ERROR |
283
|
|
|
); |
284
|
|
|
return false; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
// Don't allow duplicated addresses |
288
|
|
|
if (array_key_exists($address, $target)) { |
289
|
|
|
trigger_error( |
290
|
|
|
"<strong>Mail Class</strong>: " . $address . " has been already added in '$targetAlias'!", |
291
|
|
|
E_USER_WARNING |
292
|
|
|
); |
293
|
|
|
return false; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
if (!empty($name)) { |
297
|
|
|
// Remove CR and LF characters to prevent header injection |
298
|
|
|
$name = str_replace(array("\n", "\r"), '', $name); |
299
|
|
|
|
300
|
|
|
if (function_exists('mb_encode_mimeheader')) { |
301
|
|
|
// Encode any special characters in the displayed name |
302
|
|
|
$name = mb_encode_mimeheader($name); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
// Wrap the displayed name in quotes (to fix problems with commas etc), |
306
|
|
|
// and escape any existing quotes |
307
|
|
|
$name = '"' . str_replace('"', '\"', $name) . '"'; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
// Add the e-mail address into the target array |
311
|
|
|
$target[$address] = $name; |
312
|
|
|
// On Windows, when using PHP built-in mail drop any name, just use the e-mail address |
313
|
|
|
if (('WIN' === strtoupper(substr(PHP_OS, 0, 3))) && ('built-in' == $this->agent)) { |
314
|
|
|
$target[$address] = null; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
return true; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* Create the body of the email. |
322
|
|
|
* |
323
|
|
|
* @return void |
324
|
|
|
*/ |
325
|
|
|
private function _createBody() |
326
|
|
|
{ |
327
|
|
|
$lines = array(); |
328
|
|
|
$mainBoundary = $this->boundary; |
329
|
|
|
|
330
|
|
|
// Cleanup body |
331
|
|
|
$this->body = array(); |
332
|
|
|
|
333
|
|
|
// Add lines |
334
|
|
|
if (strpos($this->contentType, 'multipart') !== false) { |
335
|
|
|
$lines[] = 'This is a multi-part message in MIME format.'; |
336
|
|
|
$lines[] = ''; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
if (in_array($this->contentType, |
340
|
|
|
array( |
341
|
|
|
'multipart/mixed', |
342
|
|
|
'multipart/related' |
343
|
|
|
) |
344
|
|
|
) |
345
|
|
|
) { |
346
|
|
|
$lines[] = '--'.$mainBoundary; |
347
|
|
|
$this->boundary = "--=alternative=".self::createBoundary(); |
348
|
|
|
$lines[] = 'Content-Type: multipart/alternative; boundary="'.$this->boundary.'"'; |
349
|
|
|
$lines[] = ''; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
if (strpos($this->contentType, 'multipart') !== false) { |
353
|
|
|
// At least we have messageAlt and message |
354
|
|
|
if (!empty($this->messageAlt)) { |
355
|
|
|
// 1/2. messageAlt, supposed as plain text |
356
|
|
|
$lines[] = '--'.$this->boundary; |
357
|
|
|
$lines[] = 'Content-Type: text/plain; charset="'.$this->charset.'"'; |
358
|
|
|
$lines[] = 'Content-Transfer-Encoding: '.$this->contentTransferEncoding; |
359
|
|
|
$lines[] = ''; |
360
|
|
|
$lines[] = self::wrapLines(PMF_Utils::resolveMarkers($this->messageAlt, $this->_config)); |
361
|
|
|
$lines[] = ''; |
362
|
|
|
} |
363
|
|
|
// 2/2. message, supposed as, potentially, HTML |
|
|
|
|
364
|
|
|
$lines[] = '--'.$this->boundary; |
365
|
|
|
$lines[] = 'Content-Type: text/html; charset="'.$this->charset.'"'; |
366
|
|
|
$lines[] = 'Content-Transfer-Encoding: '.$this->contentTransferEncoding; |
367
|
|
|
$lines[] = ''; |
368
|
|
|
$lines[] = self::wrapLines($this->message); |
369
|
|
|
// Close the boundary delimiter |
370
|
|
|
$lines[] = '--'.$this->boundary.'--'; |
371
|
|
|
} else { |
372
|
|
|
$lines[] = self::wrapLines($this->message); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
if (in_array($this->contentType, |
376
|
|
|
array( |
377
|
|
|
'multipart/mixed', |
378
|
|
|
'multipart/related' |
379
|
|
|
) |
380
|
|
|
) |
381
|
|
|
) { |
382
|
|
|
// Back to the main boundary |
383
|
|
|
$this->boundary = $mainBoundary; |
384
|
|
|
// Add the attachments |
385
|
|
|
foreach ($this->attachments as $attachment) { |
386
|
|
|
$lines[] = '--'.$this->boundary; |
387
|
|
|
$lines[] = 'Content-Type: '.$attachment['mimetype'].'; name="'.$attachment['name'].'"'; |
388
|
|
|
$lines[] = 'Content-Transfer-Encoding: base64'; |
389
|
|
|
if ('inline' == $attachment['disposition']) { |
390
|
|
|
$lines[] = 'Content-ID: <'.$attachment['cid'].'>'; |
391
|
|
|
} |
392
|
|
|
$lines[] = 'Content-Disposition: '.$attachment['disposition'].'; filename="'.$attachment['name'].'"'; |
393
|
|
|
$lines[] = ''; |
394
|
|
|
$lines[] = chunk_split(base64_encode(file_get_contents($attachment['path']))); |
395
|
|
|
} |
396
|
|
|
// Close the boundary delimiter |
397
|
|
|
$lines[] = '--'.$this->boundary.'--'; |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
// Create the final body |
401
|
|
|
$this->body = ''; |
402
|
|
|
foreach ($lines as $line) { |
403
|
|
|
$this->body .= $line.$this->eol; |
404
|
|
|
} |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* Create the headers of the email. |
409
|
|
|
* |
410
|
|
|
* @return void |
411
|
|
|
*/ |
412
|
|
|
private function _createHeaders() |
413
|
|
|
{ |
414
|
|
|
// Cleanup headers |
415
|
|
|
$this->headers = array(); |
416
|
|
|
|
417
|
|
|
// Check if the message consists of just a "plain" single item |
418
|
|
|
if (false === strpos($this->contentType, 'multipart')) { |
419
|
|
|
// Content-Disposition: inline |
420
|
|
|
$this->headers['Content-Disposition'] = $this->contentDisposition; |
421
|
|
|
// Content-Type |
422
|
|
|
$this->headers['Content-Type'] = $this->contentType.'; format=flowed; charset="'.$this->charset.'"'; |
423
|
|
|
// Content-Transfer-Encoding: 7bit |
424
|
|
|
$this->headers['Content-Transfer-Encoding'] = '7bit'; |
425
|
|
|
} else { |
426
|
|
|
// Content-Type |
427
|
|
|
$this->headers['Content-Type'] = $this->contentType.'; boundary="'.$this->boundary.'"'; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
// Date |
431
|
|
|
$this->headers['Date'] = self::getDate(self::getTime()); |
432
|
|
|
|
433
|
|
|
// Disposition-Notification-To, RFC 3798 |
434
|
|
|
$notifyTos = array(); |
435
|
|
|
foreach($this->_notifyTo as $address => $name) { |
436
|
|
|
$notifyTos[] = (empty($name) ? '' : $name.' ').'<'.$address.'>'; |
437
|
|
|
} |
438
|
|
|
$notifyTo = implode(',', $notifyTos); |
439
|
|
|
if (!empty($notifyTo)) { |
440
|
|
|
$this->headers['Disposition-Notification-To'] = $notifyTo; |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
// From |
444
|
|
|
foreach ($this->_from as $address => $name) { |
445
|
|
|
$this->headers['From'] = (empty($name) ? '' : $name.' ') . '<' . $address . '>'; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
// CC |
449
|
|
|
foreach ($this->_cc as $address => $name) { |
450
|
|
|
$this->headers['CC'] = (empty($name) ? '' : $name.' ') . '<' . $address . '>'; |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
// BCC |
454
|
|
View Code Duplication |
foreach ($this->_bcc as $address => $name) { |
455
|
|
|
$this->headers['BCC'] = (empty($name) ? '' : $name.' ') . '<' . $address . '>'; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
// Message-Id |
459
|
|
|
$this->headers['Message-ID'] = $this->messageId; |
460
|
|
|
|
461
|
|
|
// MIME-Version: 1.0 |
462
|
|
|
$this->headers['MIME-Version'] = '1.0'; |
463
|
|
|
|
464
|
|
|
// Reply-To |
465
|
|
|
$this->headers['Reply-To'] = $this->headers['From']; |
466
|
|
|
foreach($this->_replyTo as $address => $name) { |
467
|
|
|
$this->headers['Reply-To'] = (empty($name) ? '' : $name.' ').'<'.$address.'>'; |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
// Return-Path |
471
|
|
|
foreach($this->_from as $address => $name) { |
472
|
|
|
$this->headers['Return-Path'] = '<'.$address.'>'; |
473
|
|
|
} |
474
|
|
|
foreach($this->_returnPath as $address => $name) { |
475
|
|
|
$this->headers['Return-Path'] = '<'.$address.'>'; |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
// Sender |
479
|
|
|
$this->headers['Sender'] = $this->headers['From']; |
480
|
|
|
foreach($this->_sender as $address => $name) { |
481
|
|
|
$this->headers['Sender'] = (empty($name) ? '' : $name.' ').'<'.$address.'>'; |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
// Subject. Note: it must be RFC 2047 compliant |
485
|
|
|
// TODO: wrap mb_encode_mimeheader() to add other content encodings |
486
|
|
|
$this->headers['Subject'] = PMF_Utils::resolveMarkers( |
487
|
|
|
html_entity_decode($this->subject, ENT_COMPAT, 'UTF-8'), |
488
|
|
|
$this->_config |
489
|
|
|
); |
490
|
|
|
|
491
|
|
|
// X-Mailer |
492
|
|
|
$this->headers['X-Mailer'] = $this->_mailer; |
493
|
|
|
|
494
|
|
|
// X-MSMail-Priority |
495
|
|
|
if (isset($this->priorities[(int)$this->priority])) { |
496
|
|
|
$this->headers['X-MSMail-Priority'] = $this->priorities[(int)$this->priority]; |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
// X-Originating-IP |
500
|
|
|
if (isset($_SERVER['REMOTE_ADDR'])) { |
501
|
|
|
$this->headers['X-Originating-IP'] = $_SERVER['REMOTE_ADDR']; |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
// X-Priority |
505
|
|
|
$this->headers['X-Priority'] = $this->priority; |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
/** |
509
|
|
|
* Set just one e-mail address into an array. |
510
|
|
|
* |
511
|
|
|
* @param array $target Target array. |
512
|
|
|
* @param string $targetAlias Alias Target alias. |
513
|
|
|
* @param string $address User e-mail address. |
514
|
|
|
* @param string $name User name (optional). |
515
|
|
|
* |
516
|
|
|
* @return bool True if successful, false otherwise. |
517
|
|
|
*/ |
518
|
|
|
private function _setEmailTo(&$target, $targetAlias, $address, $name = null) |
519
|
|
|
{ |
520
|
|
|
// Check for the permitted number of items into the $target array |
521
|
|
|
if (count($target) > 0) { |
522
|
|
|
$keys = array_keys($target); |
523
|
|
|
trigger_error( |
524
|
|
|
"<strong>Mail Class</strong>: a valid e-mail address, $keys[0], has been already added as '$targetAlias'!", |
525
|
|
|
E_USER_ERROR |
526
|
|
|
); |
527
|
|
|
return false; |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
return $this->_addEmailTo($target, $targetAlias, $address, $name); |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
/** |
534
|
|
|
* Add an attachment. |
535
|
|
|
* |
536
|
|
|
* @param string $path File path. |
537
|
|
|
* @param string $name File name. Defaults to the basename. |
538
|
|
|
* @param string $mimetype File MIME type. Defaults to 'application/octet-stream'. |
539
|
|
|
* @param string $disposition Attachment disposition. Defaults to 'attachment'. |
540
|
|
|
* @param string $cid Content ID, required when disposition is 'inline'. Defaults to ''. |
541
|
|
|
* @return bool True if successful, false otherwise. |
542
|
|
|
*/ |
543
|
|
|
public function addAttachment($path, $name = null, $mimetype = 'application/octet-stream', $disposition = 'attachment', $cid = '') |
544
|
|
|
{ |
545
|
|
|
if (!file_exists($path)) { |
546
|
|
|
// File not found |
547
|
|
|
return false; |
548
|
|
|
} else if (('inline' == $disposition) && empty($cid)) { |
549
|
|
|
// Content ID is required |
550
|
|
|
return false; |
551
|
|
|
} else { |
552
|
|
|
if (empty($name)) { |
553
|
|
|
$name = basename($path); |
554
|
|
|
} |
555
|
|
|
|
556
|
|
|
$this->attachments[] = array( |
557
|
|
|
"cid" => $cid, |
558
|
|
|
"disposition" => $disposition, |
559
|
|
|
"mimetype" => $mimetype, |
560
|
|
|
"name" => $name, |
561
|
|
|
"path" => $path |
562
|
|
|
); |
563
|
|
|
|
564
|
|
|
return true; |
565
|
|
|
} |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
/** |
569
|
|
|
* Add a recipient as <BCC>. |
570
|
|
|
* |
571
|
|
|
* @param string $address User e-mail address. |
572
|
|
|
* @param string $name User name (optional). |
573
|
|
|
* @return bool True if successful, false otherwise. |
574
|
|
|
*/ |
575
|
|
|
public function addBcc($address, $name = null) |
576
|
|
|
{ |
577
|
|
|
return $this->_addEmailTo($this->_bcc, 'Bcc', $address, $name); |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
/** |
581
|
|
|
* Add a recipient as <CC>. |
582
|
|
|
* |
583
|
|
|
* @param string $address User e-mail address. |
584
|
|
|
* @param string $name User name (optional). |
585
|
|
|
* @return bool True if successful, false otherwise. |
586
|
|
|
*/ |
587
|
|
|
public function addCc($address, $name = null) |
588
|
|
|
{ |
589
|
|
|
return $this->_addEmailTo($this->_cc, 'Cc', $address, $name); |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
/** |
593
|
|
|
* Add an address to send a notification to. |
594
|
|
|
* |
595
|
|
|
* @param string $address User e-mail address. |
596
|
|
|
* @param string $name User name (optional). |
597
|
|
|
* @return bool True if successful, false otherwise. |
598
|
|
|
*/ |
599
|
|
|
public function addNotificationTo($address, $name = null) |
600
|
|
|
{ |
601
|
|
|
return $this->_addEmailTo($this->_notifyTo, 'Disposition-Notification-To', $address, $name); |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
/** |
605
|
|
|
* Add a recipient as <TO>. |
606
|
|
|
* |
607
|
|
|
* @param string $address User e-mail address. |
608
|
|
|
* @param string $name User name (optional). |
609
|
|
|
* @return bool True if successful, false otherwise. |
610
|
|
|
*/ |
611
|
|
|
public function addTo($address, $name = null) |
612
|
|
|
{ |
613
|
|
|
return $this->_addEmailTo($this->_to, 'To', $address, $name); |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
/** |
617
|
|
|
* Create a string to be used as a valid boundary value. |
618
|
|
|
* |
619
|
|
|
* @static |
620
|
|
|
* @return string The boundary value. |
621
|
|
|
*/ |
622
|
|
|
public static function createBoundary() |
623
|
|
|
{ |
624
|
|
|
return '-----' .md5(microtime()); |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
/** |
628
|
|
|
* Returns the given text being sure that any CR or LF has been fixed |
629
|
|
|
* according with RFC 2822 EOL setting. |
630
|
|
|
* |
631
|
|
|
* @param string $text Text with a mixed usage of CR, LF, CRLF. |
632
|
|
|
* @return string The fixed text. |
633
|
|
|
* @see eol |
634
|
|
|
*/ |
635
|
|
|
public function fixEOL($text) |
636
|
|
|
{ |
637
|
|
|
// Assure that anything among CRLF, CR will be replaced with just LF |
638
|
|
|
$text = str_replace( |
639
|
|
|
array( |
640
|
|
|
"\r\n",// CRLF |
641
|
|
|
"\r", // CR |
642
|
|
|
"\n",// LF |
643
|
|
|
), |
644
|
|
|
"\n", // LF |
645
|
|
|
$text |
646
|
|
|
); |
647
|
|
|
// Set any LF to the RFC 2822 EOL |
648
|
|
|
$text = str_replace("\n", $this->eol, $text); |
649
|
|
|
|
650
|
|
|
return $text; |
651
|
|
|
} |
652
|
|
|
|
653
|
|
|
/** |
654
|
|
|
* Returns the date according with RFC 2822. |
655
|
|
|
* |
656
|
|
|
* @static |
657
|
|
|
* @param string $date Unix timestamp. |
658
|
|
|
* @return string The RFC 2822 date if successful, false otherwise. |
659
|
|
|
*/ |
660
|
|
|
public static function getDate($date) |
661
|
|
|
{ |
662
|
|
|
$rfc2822Date = date('r', $date); |
663
|
|
|
|
664
|
|
|
return $rfc2822Date; |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
/** |
668
|
|
|
* Returns the Unix timestamp with preference to the Page Request time. |
669
|
|
|
* |
670
|
|
|
* @static |
671
|
|
|
* @return int Unix timestamp. |
672
|
|
|
*/ |
673
|
|
|
public static function getTime() |
674
|
|
|
{ |
675
|
|
|
if (isset($_SERVER['REQUEST_TIME'])) { |
676
|
|
|
return $_SERVER['REQUEST_TIME']; |
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
return time(); |
680
|
|
|
} |
681
|
|
|
|
682
|
|
|
/** |
683
|
|
|
* Get the instance of the class implementing the MUA for the given type. |
684
|
|
|
* |
685
|
|
|
* @static |
686
|
|
|
* @param string $mua Type of the MUA. |
687
|
|
|
* |
688
|
|
|
* @return mixed The class instance if successful, false otherwise. |
689
|
|
|
*/ |
690
|
|
|
public static function getMUA($mua) |
691
|
|
|
{ |
692
|
|
|
$impl = ucfirst( |
693
|
|
|
str_replace( |
694
|
|
|
'-', |
695
|
|
|
'', |
696
|
|
|
$mua |
697
|
|
|
) |
698
|
|
|
); |
699
|
|
|
$class = 'PMF_Mail_'.$impl; |
700
|
|
|
|
701
|
|
|
return new $class; |
702
|
|
|
} |
703
|
|
|
|
704
|
|
|
/** |
705
|
|
|
* Returns the server name. |
706
|
|
|
* |
707
|
|
|
* @static |
708
|
|
|
* @return string The server name. |
709
|
|
|
*/ |
710
|
|
|
public static function getServerName() |
711
|
|
|
{ |
712
|
|
|
$hostname = 'localhost.localdomain'; |
713
|
|
View Code Duplication |
if (isset($_SERVER['HTTP_HOST'])) { |
714
|
|
|
$hostname = $_SERVER['HTTP_HOST']; |
715
|
|
|
} else if (isset($_SERVER['SERVER_NAME'])) { |
716
|
|
|
$hostname = $_SERVER['SERVER_NAME']; |
717
|
|
|
} |
718
|
|
|
|
719
|
|
|
return $hostname; |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
/** |
723
|
|
|
* Send the e-mail according with the current settings. |
724
|
|
|
* |
725
|
|
|
* @return bool True if successful, false otherwise. |
726
|
|
|
* |
727
|
|
|
* @todo Enhance error handling using exceptions |
728
|
|
|
*/ |
729
|
|
|
public function send() |
730
|
|
|
{ |
731
|
|
|
// Sanity check |
732
|
|
|
if (count($this->_to) + count($this->_cc) + count($this->_bcc) < 1) { |
733
|
|
|
trigger_error( |
734
|
|
|
"<strong>Mail Class</strong>: you need at least to set one recipient among TO, CC and BCC!", |
735
|
|
|
E_USER_ERROR |
736
|
|
|
); |
737
|
|
|
return false; |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
// Has any alternative message been provided? |
741
|
|
|
if (!empty($this->messageAlt)) { |
742
|
|
|
$this->contentType = 'multipart/alternative'; |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
// Has any attachment been provided? |
746
|
|
|
if (!empty($this->attachments)) { |
747
|
|
|
$this->contentType = 'multipart/mixed'; |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
// Has any in-line attachment been provided? |
751
|
|
|
$hasInlineAttachments = false; |
752
|
|
|
$idx = 0; |
753
|
|
|
while (!$hasInlineAttachments && ($idx < count($this->attachments))) { |
754
|
|
|
$hasInlineAttachments = ('inline' == $this->attachments[$idx]['disposition']); |
755
|
|
|
$idx++; |
756
|
|
|
} |
757
|
|
|
|
758
|
|
|
if ($hasInlineAttachments) { |
759
|
|
|
$this->contentType = 'multipart/related'; |
760
|
|
|
} |
761
|
|
|
|
762
|
|
|
// A valid MUA needs to implement the PMF_Mail_IMUA interface |
763
|
|
|
// i.e. we must prepare recipients, headers, body for the send() method |
764
|
|
|
|
765
|
|
|
// Prepare the recipients |
766
|
|
|
$to = array(); |
767
|
|
View Code Duplication |
foreach($this->_to as $address => $name) { |
768
|
|
|
$to[] = (empty($name) ? '' : $name.' ').'<'.$address.'>'; |
769
|
|
|
} |
770
|
|
|
$recipients = implode(',', $to); |
771
|
|
|
// Check for the need of undisclosed recipients outlook-like <TO:> |
772
|
|
|
if (empty($recipients) && (0 == count($this->_cc))) { |
773
|
|
|
$recipients = '<Undisclosed-Recipient:;>'; |
774
|
|
|
} |
775
|
|
|
|
776
|
|
|
// Prepare the headers |
777
|
|
|
$this->_createHeaders(); |
778
|
|
|
|
779
|
|
|
// Prepare the body |
780
|
|
|
$this->_createBody(); |
781
|
|
|
|
782
|
|
|
// Send the email adopting to the given MUA |
783
|
|
|
$mua = self::getMUA($this->agent); |
784
|
|
|
switch ($this->agent) { |
785
|
|
|
case 'built-in': |
786
|
|
|
$sent = $mua->send($recipients, $this->headers, $this->body); |
787
|
|
|
break; |
788
|
|
|
default: |
789
|
|
|
trigger_error( |
790
|
|
|
"<strong>Mail Class</strong>: $this->agent has no implementation!", |
791
|
|
|
E_USER_ERROR |
792
|
|
|
); |
793
|
|
|
$sent = false; |
794
|
|
|
} |
795
|
|
|
|
796
|
|
|
return $sent; |
797
|
|
|
} |
798
|
|
|
|
799
|
|
|
/** |
800
|
|
|
* Set the "From" address. |
801
|
|
|
* |
802
|
|
|
* @param string $address User e-mail address. |
803
|
|
|
* @param string $name User name (optional). |
804
|
|
|
* @return bool True if successful, false otherwise. |
805
|
|
|
*/ |
806
|
|
|
public function setFrom($address, $name = null) |
807
|
|
|
{ |
808
|
|
|
return $this->_setEmailTo($this->_from, 'From', $address, $name); |
809
|
|
|
} |
810
|
|
|
|
811
|
|
|
/** |
812
|
|
|
* Set an HTML message providing also a plain text alternative message, |
813
|
|
|
* if not already set using the $messageAlt property. |
814
|
|
|
* Besides it is possible to put resources as inline attachments |
815
|
|
|
* |
816
|
|
|
* @param string $message HTML message. |
817
|
|
|
* @param bool $sanitize Strip out potentially unsecured HTML tags. Defaults to false. |
818
|
|
|
* @param bool $inline Add images as inline attachments. Defaults to false. |
819
|
|
|
* |
820
|
|
|
* @return void |
821
|
|
|
*/ |
822
|
|
|
public function setHTMLMessage($message, $sanitize = false, $inline = false) |
823
|
|
|
{ |
824
|
|
|
// No Javascript at all |
825
|
|
|
// 1/2. <script blahblahblah>blahblahblah</tag> |
826
|
|
|
$message = PMF_String::preg_replace( |
827
|
|
|
'/(<script[^>]*>.*<\/script>)|<script[^\/]*\/>|<script[^\/]*>/is', |
828
|
|
|
'', |
829
|
|
|
$message |
830
|
|
|
); |
831
|
|
|
|
832
|
|
|
// Cleanup potentially dangerous HTML tags: |
833
|
|
|
if ($sanitize) { |
834
|
|
|
// 1/2. <tag blahblahblah>blahblahblah</tag> |
835
|
|
|
$message = PMF_String::preg_replace( |
836
|
|
|
'/<(applet|embed|head|meta|object|style|title)[^>]*>.*<\/\\1>/is', |
837
|
|
|
'', |
838
|
|
|
$message |
|
|
|
|
839
|
|
|
); |
840
|
|
|
// 2/2. <tag blahblahblah /> |
841
|
|
|
$message = PMF_String::preg_replace( |
842
|
|
|
'/<(applet|embed|head|meta|object|style|title)[^\/]*\/>/is', |
843
|
|
|
'', |
844
|
|
|
$message |
|
|
|
|
845
|
|
|
); |
846
|
|
|
} |
847
|
|
|
|
848
|
|
|
if ($inline) { |
849
|
|
|
trigger_error( |
850
|
|
|
"<strong>Mail Class</strong>: inline option is not implemented yet.", |
851
|
|
|
E_USER_ERROR |
852
|
|
|
); |
853
|
|
|
} |
854
|
|
|
|
855
|
|
|
// Set the HTML text as the main message |
856
|
|
|
$this->message = trim($message); |
857
|
|
|
|
858
|
|
|
// If no alternative text has been provided yet, use just |
859
|
|
|
// the HTML message stripping any HTML tag |
860
|
|
|
if (empty($this->messageAlt)) { |
861
|
|
|
$this->messageAlt = trim(strip_tags($this->message)); |
862
|
|
|
} |
863
|
|
|
} |
864
|
|
|
|
865
|
|
|
/** |
866
|
|
|
* Set the "Reply-to" address. |
867
|
|
|
* |
868
|
|
|
* @param string $address User e-mail address. |
869
|
|
|
* @param string $name User name (optional). |
870
|
|
|
* @return bool True if successful, false otherwise. |
871
|
|
|
*/ |
872
|
|
|
public function setReplyTo($address, $name = null) |
873
|
|
|
{ |
874
|
|
|
return $this->_setEmailTo($this->_replyTo, 'Reply-To', $address, $name); |
875
|
|
|
} |
876
|
|
|
|
877
|
|
|
/** |
878
|
|
|
* Set the "Return-Path" address. |
879
|
|
|
* |
880
|
|
|
* @param string $address User e-mail address. |
881
|
|
|
* @return bool True if successful, false otherwise. |
882
|
|
|
*/ |
883
|
|
|
public function setReturnPath($address) |
884
|
|
|
{ |
885
|
|
|
return $this->_setEmailTo($this->_returnPath, 'Return-Path', $address); |
886
|
|
|
} |
887
|
|
|
|
888
|
|
|
/** |
889
|
|
|
* Set the "Sender" address. |
890
|
|
|
* |
891
|
|
|
* @param string $address User e-mail address. |
892
|
|
|
* @param string $name User name (optional). |
893
|
|
|
* @return bool True if successful, false otherwise. |
894
|
|
|
*/ |
895
|
|
|
public function setSender($address, $name = null) |
896
|
|
|
{ |
897
|
|
|
return $this->_setEmailTo($this->_sender, 'Sender', $address, $name); |
898
|
|
|
} |
899
|
|
|
|
900
|
|
|
/** |
901
|
|
|
* Remove any previous "From" address. |
902
|
|
|
* |
903
|
|
|
* @return bool True if successful, false otherwise. |
904
|
|
|
*/ |
905
|
|
|
public function unsetFrom() |
906
|
|
|
{ |
907
|
|
|
$this->_from = array(); |
908
|
|
|
|
909
|
|
|
return true; |
910
|
|
|
} |
911
|
|
|
|
912
|
|
|
/** |
913
|
|
|
* Validate an address as an e-mail address. |
914
|
|
|
* |
915
|
|
|
* @param string $address E-Mail address |
916
|
|
|
* |
917
|
|
|
* @return bool True if the given address is a valid e-mail address, false otherwise. |
918
|
|
|
*/ |
919
|
|
|
public static function validateEmail($address) |
920
|
|
|
{ |
921
|
|
|
if (empty($address)) { |
922
|
|
|
return false; |
923
|
|
|
} |
924
|
|
|
|
925
|
|
|
if (PMF_String::strpos($address, '\0') !== false) { |
926
|
|
|
return false; |
927
|
|
|
} |
928
|
|
|
|
929
|
|
|
$unsafe = array ("\r", "\n"); |
930
|
|
|
if ($address !== str_replace($unsafe, '', $address)) { |
931
|
|
|
return false; |
932
|
|
|
} |
933
|
|
|
|
934
|
|
|
if (false === filter_var($address, FILTER_VALIDATE_EMAIL)) { |
935
|
|
|
return false; |
936
|
|
|
} |
937
|
|
|
|
938
|
|
|
return true; |
939
|
|
|
} |
940
|
|
|
|
941
|
|
|
/** |
942
|
|
|
* Wraps the lines contained into the given message. |
943
|
|
|
* |
944
|
|
|
* @param string $message Message. |
945
|
|
|
* @param integer $width Column width. Defaults to 72. |
946
|
|
|
* @param boolean $cut Cutting a word is allowed. Defaults to false. |
947
|
|
|
* |
948
|
|
|
* @return string The given message, wrapped as requested. |
949
|
|
|
*/ |
950
|
|
|
public function wrapLines($message, $width = 72, $cut = false) |
951
|
|
|
{ |
952
|
|
|
$message = $this->fixEOL($message); |
953
|
|
|
|
954
|
|
|
if (PMF_String::strpos(strtolower($this->charset), 'utf') !== false) { |
955
|
|
|
// PHP wordwrap() is not safe with multibyte UTF chars |
956
|
|
|
return $message; |
957
|
|
|
} else { |
958
|
|
|
$lines = explode($this->eol, $message); |
959
|
|
|
$wrapped = ''; |
960
|
|
|
foreach ($lines as $value) { |
961
|
|
|
$wrapped .= (empty($wrapped) ? '' : $this->eol); |
962
|
|
|
$wrapped .= wordwrap($value, $width, $this->eol, $cut); |
963
|
|
|
} |
964
|
|
|
|
965
|
|
|
return $wrapped; |
966
|
|
|
} |
967
|
|
|
} |
968
|
|
|
|
969
|
|
|
/** |
970
|
|
|
* If the email spam protection has been activated from the general |
971
|
|
|
* phpMyFAQ configuration this method converts an email address e.g. |
972
|
|
|
* from "[email protected]" to "user_AT_example_DOT_org". Otherwise |
973
|
|
|
* it will return the plain email address. |
974
|
|
|
* |
975
|
|
|
* @param string $email E-mail address |
976
|
|
|
* @static |
977
|
|
|
* |
978
|
|
|
* @return string |
979
|
|
|
*/ |
980
|
|
|
public function safeEmail($email) |
981
|
|
|
{ |
982
|
|
|
if ($this->_config->get('spam.enableSafeEmail')) { |
983
|
|
|
return str_replace ( array ('@', '.' ), array ('_AT_', '_DOT_' ), $email ); |
984
|
|
|
} else { |
985
|
|
|
return $email; |
986
|
|
|
} |
987
|
|
|
} |
988
|
|
|
} |
989
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.