1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Jacobemerick\Archangel; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* This is the main class for Archangel mailer |
7
|
|
|
* For licensing and examples: |
8
|
|
|
* @see https://github.com/jacobemerick/archangel |
9
|
|
|
* |
10
|
|
|
* @author jacobemerick (http://home.jacobemerick.com/) |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
class Archangel |
14
|
|
|
{ |
15
|
|
|
|
16
|
|
|
/** @var string $subject */ |
17
|
|
|
protected $subject; |
18
|
|
|
|
19
|
|
|
/** @var array $to */ |
20
|
|
|
protected $to = array(); |
21
|
|
|
|
22
|
|
|
/** @var array $cc */ |
23
|
|
|
protected $cc = array(); |
24
|
|
|
|
25
|
|
|
/** @var array $bcc */ |
26
|
|
|
protected $bcc = array(); |
27
|
|
|
|
28
|
|
|
/** @var array $headers */ |
29
|
|
|
protected $headers = array(); |
30
|
|
|
|
31
|
|
|
/** @var string $plainMessage */ |
32
|
|
|
protected $plainMessage; |
33
|
|
|
|
34
|
|
|
/** @var string $htmlMessage */ |
35
|
|
|
protected $htmlMessage; |
36
|
|
|
|
37
|
|
|
/** @var array $attachments */ |
38
|
|
|
protected $attachments = array(); |
39
|
|
|
|
40
|
|
|
/** @var string MAILER_PATTERN */ |
41
|
|
|
const MAILER_PATTERN = "PHP/%s"; |
42
|
|
|
|
43
|
|
|
/** @var string LINE_BREAK */ |
44
|
|
|
const LINE_BREAK = "\r\n"; |
45
|
|
|
|
46
|
|
|
/** @var string BOUNDARY_PATTERN */ |
47
|
|
|
const BOUNDARY_PATTERN = "PHP-mixed-%s"; |
48
|
|
|
|
49
|
|
|
/** @var string BOUNDARY_SALT */ |
50
|
|
|
const BOUNDARY_SALT = "Boundary Salt"; |
51
|
|
|
|
52
|
|
|
/** @var string ALTERNATIVE_BOUNDARY_PATTERN */ |
53
|
|
|
const ALTERNATIVE_BOUNDARY_PATTERN = "PHP-alternative-%s"; |
54
|
|
|
|
55
|
|
|
/** @var string ALTERNATIVE_BOUNDARY_SALT */ |
56
|
|
|
const ALTERNATIVE_BOUNDARY_SALT = "Alternative Boundary Salt"; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @param string $mailer |
60
|
|
|
*/ |
61
|
|
|
public function __construct($mailer = null) |
62
|
|
|
{ |
63
|
|
|
if (is_null($mailer)) { |
64
|
|
|
$mailer = sprintf(self::MAILER_PATTERN, phpversion()); |
65
|
|
|
} |
66
|
|
|
$this->headers['X-Mailer'] = $mailer; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Setter method for adding recipients |
71
|
|
|
* |
72
|
|
|
* @param string $address email address for the recipient |
73
|
|
|
* @param string $title name of the recipient (optional) |
74
|
|
|
|
75
|
|
|
* @return object instantiated $this |
76
|
|
|
*/ |
77
|
|
View Code Duplication |
public function addTo($address, $title = '') |
|
|
|
|
78
|
|
|
{ |
79
|
|
|
if (!empty($title)) { |
80
|
|
|
$address = sprintf('"%s" <%s>', $title, $address); |
81
|
|
|
} |
82
|
|
|
array_push($this->to, $address); |
83
|
|
|
|
84
|
|
|
return $this; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Setter method for adding cc recipients |
89
|
|
|
* |
90
|
|
|
* @param string $address email address for the cc recipient |
91
|
|
|
* @param string $title name of the cc recipient (optional) |
92
|
|
|
* |
93
|
|
|
* @return object instantiated $this |
94
|
|
|
*/ |
95
|
|
View Code Duplication |
public function addCC($address, $title = '') |
|
|
|
|
96
|
|
|
{ |
97
|
|
|
if (!empty($title)) { |
98
|
|
|
$address = sprintf('"%s" <%s>', $title, $address); |
99
|
|
|
} |
100
|
|
|
array_push($this->cc, $address); |
101
|
|
|
|
102
|
|
|
return $this; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Setter method for adding bcc recipients |
107
|
|
|
* |
108
|
|
|
* @param string $addres email address for the bcc recipient |
|
|
|
|
109
|
|
|
* @param string $title name of the bcc recipient (optional) |
110
|
|
|
* |
111
|
|
|
* @return object instantiated $this |
112
|
|
|
*/ |
113
|
|
View Code Duplication |
public function addBCC($address, $title = '') |
|
|
|
|
114
|
|
|
{ |
115
|
|
|
if (!empty($title)) { |
116
|
|
|
$address = sprintf('"%s" <%s>', $title, $address); |
117
|
|
|
} |
118
|
|
|
array_push($this->bcc, $address); |
119
|
|
|
|
120
|
|
|
return $this; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Setter method for setting the single 'from' field |
125
|
|
|
* |
126
|
|
|
* @param string $address email address for the sender |
127
|
|
|
* @param string $title name of the sender (optional) |
128
|
|
|
* |
129
|
|
|
* @return object instantiated $this |
130
|
|
|
*/ |
131
|
|
View Code Duplication |
public function setFrom($address, $title = '') |
|
|
|
|
132
|
|
|
{ |
133
|
|
|
if (!empty($title)) { |
134
|
|
|
$address = sprintf('"%s" <%s>', $title, $address); |
135
|
|
|
} |
136
|
|
|
$this->headers['From'] = $address; |
137
|
|
|
|
138
|
|
|
return $this; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Setter method for setting the single 'reply-to' field |
143
|
|
|
* |
144
|
|
|
* @param string $address email address for the reply-to |
145
|
|
|
* @param string $title name of the reply-to (optional) |
146
|
|
|
* |
147
|
|
|
* @return object instantiated $this |
148
|
|
|
*/ |
149
|
|
View Code Duplication |
public function setReplyTo($address, $title = '') |
|
|
|
|
150
|
|
|
{ |
151
|
|
|
if (!empty($title)) { |
152
|
|
|
$address = sprintf('"%s" <%s>', $title, $address); |
153
|
|
|
} |
154
|
|
|
$this->headers['Reply-To'] = $address; |
155
|
|
|
|
156
|
|
|
return $this; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Setter method for setting a subject |
161
|
|
|
* |
162
|
|
|
* @param string $subject subject for the email |
163
|
|
|
* |
164
|
|
|
* @return object instantiated $this |
165
|
|
|
*/ |
166
|
|
|
public function setSubject($subject) |
167
|
|
|
{ |
168
|
|
|
$this->subject = $subject; |
169
|
|
|
|
170
|
|
|
return $this; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Setter method for the plain text message |
175
|
|
|
* |
176
|
|
|
* @param string $message the plain-text message |
177
|
|
|
* |
178
|
|
|
* @return object instantiated $this |
179
|
|
|
*/ |
180
|
|
|
public function setPlainMessage($message) |
181
|
|
|
{ |
182
|
|
|
$this->plainMessage = $message; |
183
|
|
|
|
184
|
|
|
return $this; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Setter method for the html message |
189
|
|
|
* |
190
|
|
|
* @param string $message the html message |
191
|
|
|
* |
192
|
|
|
* @return object instantiated $this |
193
|
|
|
*/ |
194
|
|
|
public function setHTMLMessage($message) |
195
|
|
|
{ |
196
|
|
|
$this->htmlMessage = $message; |
197
|
|
|
|
198
|
|
|
return $this; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Setter method for adding attachments |
203
|
|
|
* |
204
|
|
|
* @param string $path the full path of the attachment |
205
|
|
|
* @param string $type mime type of the file |
206
|
|
|
* @param string $title the title of the attachment (optional) |
207
|
|
|
* |
208
|
|
|
* @return object instantiated $this |
209
|
|
|
*/ |
210
|
|
|
public function addAttachment($path, $type, $title = '') |
211
|
|
|
{ |
212
|
|
|
array_push($this->attachments, array( |
213
|
|
|
'path' => $path, |
214
|
|
|
'type' => $type, |
215
|
|
|
'title' => $title, |
216
|
|
|
)); |
217
|
|
|
|
218
|
|
|
return $this; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* The executing step, the actual sending of the email |
223
|
|
|
* First checks to make sure the minimum fields are set (returns false if they are not) |
224
|
|
|
* Second it attempts to send the mail with php's mail() (returns false if it fails) |
225
|
|
|
* |
226
|
|
|
* return boolean whether or not the email was valid & sent |
227
|
|
|
*/ |
228
|
|
|
public function send() |
229
|
|
|
{ |
230
|
|
|
if($this->passed_validation === FALSE) |
|
|
|
|
231
|
|
|
return false; |
232
|
|
|
|
233
|
|
|
if(!$this->check_required_fields()) |
234
|
|
|
return false; |
235
|
|
|
|
236
|
|
|
$to = $this->get_to(); |
237
|
|
|
$subject = $this->subject; |
238
|
|
|
$message = $this->get_message(); |
239
|
|
|
$additional_headers = $this->get_additional_headers(); |
240
|
|
|
|
241
|
|
|
return mail($to, $subject, $message, $additional_headers); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Main instantiator for the class |
246
|
|
|
* |
247
|
|
|
* @return object instantiated $this |
248
|
|
|
*/ |
249
|
|
|
public static function instance() |
250
|
|
|
{ |
251
|
|
|
return new Archangel(); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Private call to check the minimum required fields |
256
|
|
|
* |
257
|
|
|
* @return boolean whether or not the email meets the minimum required fields |
258
|
|
|
*/ |
259
|
|
|
private function check_required_fields() |
260
|
|
|
{ |
261
|
|
|
return ( |
262
|
|
|
count($this->to_array) > 0 && |
|
|
|
|
263
|
|
|
(isset($this->subject) && strlen($this->subject) > 0) && |
264
|
|
|
( |
265
|
|
|
(isset($this->plain_message) && strlen($this->plain_message) > 0) || |
|
|
|
|
266
|
|
|
(isset($this->html_message) && strlen($this->html_message) > 0) || |
|
|
|
|
267
|
|
|
(isset($this->attachment_array) && count($this->attachment_array) > 0))); |
|
|
|
|
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Private function to collect the recipients from to_array |
272
|
|
|
* |
273
|
|
|
* @return string comma-separated lit of recipients |
274
|
|
|
*/ |
275
|
|
|
private function get_to() |
276
|
|
|
{ |
277
|
|
|
return implode(', ', $this->to_array); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Long, nasty creater of the actual message, with all the multipart logic you'd never want to see |
282
|
|
|
* |
283
|
|
|
* @return string email message |
284
|
|
|
*/ |
285
|
|
|
private function get_message() |
286
|
|
|
{ |
287
|
|
|
$message = ''; |
288
|
|
|
|
289
|
|
|
if(isset($this->attachment_array) && count($this->attachment_array) > 0) |
290
|
|
|
$message .= "--{$this->get_boundary()}" . self::$LINE_BREAK; |
291
|
|
|
|
292
|
|
|
if( |
293
|
|
|
isset($this->plain_message) && strlen($this->plain_message) > 0 && |
294
|
|
|
isset($this->html_message) && strlen($this->html_message) > 0) |
295
|
|
|
{ |
296
|
|
View Code Duplication |
if(isset($this->attachment_array) && count($this->attachment_array) > 0) |
|
|
|
|
297
|
|
|
{ |
298
|
|
|
$message .= "Content-Type: multipart/alternative; boundary={$this->get_alternative_boundary()}" . self::$LINE_BREAK; |
299
|
|
|
$message .= self::$LINE_BREAK; |
300
|
|
|
} |
301
|
|
|
$message .= "--{$this->get_alternative_boundary()}" . self::$LINE_BREAK; |
302
|
|
|
$message .= 'Content-Type: text/plain; charset="iso-8859"' . self::$LINE_BREAK; |
303
|
|
|
$message .= 'Content-Transfer-Encoding: 7bit' . self::$LINE_BREAK; |
304
|
|
|
$message .= self::$LINE_BREAK; |
305
|
|
|
$message .= $this->plain_message; |
306
|
|
|
$message .= self::$LINE_BREAK; |
307
|
|
|
$message .= "--{$this->get_alternative_boundary()}" . self::$LINE_BREAK; |
308
|
|
|
$message .= 'Content-Type: text/html; charset="iso-8859-1"' . self::$LINE_BREAK; |
309
|
|
|
$message .= 'Content-Transfer-Encoding: 7bit' . self::$LINE_BREAK; |
310
|
|
|
$message .= self::$LINE_BREAK; |
311
|
|
|
$message .= $this->html_message; |
312
|
|
|
$message .= self::$LINE_BREAK; |
313
|
|
|
$message .= "--{$this->get_alternative_boundary()}--" . self::$LINE_BREAK; |
314
|
|
|
$message .= self::$LINE_BREAK; |
315
|
|
|
} |
316
|
|
|
else if(isset($this->plain_message) && strlen($this->plain_message)) |
317
|
|
|
{ |
318
|
|
View Code Duplication |
if(isset($this->attachment_array) && count($this->attachment_array) > 0) |
|
|
|
|
319
|
|
|
{ |
320
|
|
|
$message .= 'Content-Type: text/plain; charset="iso-8859"' . self::$LINE_BREAK; |
321
|
|
|
$message .= 'Content-Transfer-Encoding: 7bit' . self::$LINE_BREAK; |
322
|
|
|
$message .= self::$LINE_BREAK; |
323
|
|
|
} |
324
|
|
|
$message .= $this->plain_message; |
325
|
|
|
$message .= self::$LINE_BREAK; |
326
|
|
|
} |
327
|
|
|
else if(isset($this->html_message) && strlen($this->html_message)) |
328
|
|
|
{ |
329
|
|
View Code Duplication |
if(isset($this->attachment_array) && count($this->attachment_array) > 0) |
|
|
|
|
330
|
|
|
{ |
331
|
|
|
$message .= 'Content-Type: text/html; charset="iso-8859-1"' . self::$LINE_BREAK; |
332
|
|
|
$message .= 'Content-Transfer-Encoding: 7bit' . self::$LINE_BREAK; |
333
|
|
|
$message .= self::$LINE_BREAK; |
334
|
|
|
} |
335
|
|
|
$message .= $this->html_message; |
336
|
|
|
$message .= self::$LINE_BREAK; |
337
|
|
|
} |
338
|
|
|
if(isset($this->attachment_array) && count($this->attachment_array) > 0) |
339
|
|
|
{ |
340
|
|
|
foreach($this->attachment_array as $attachment) |
341
|
|
|
{ |
342
|
|
|
$message .= "--{$this->get_boundary()}" . self::$LINE_BREAK; |
343
|
|
|
$message .= "Content-Type: {$attachment->type}; name=\"{$attachment->title}\"" . self::$LINE_BREAK; |
344
|
|
|
$message .= 'Content-Transfer-Encoding: base64' . self::$LINE_BREAK; |
345
|
|
|
$message .= 'Content-Disposition: attachment' . self::$LINE_BREAK; |
346
|
|
|
$message .= self::$LINE_BREAK; |
347
|
|
|
$message .= $this->get_attachment_content($attachment); |
348
|
|
|
$message .= self::$LINE_BREAK; |
349
|
|
|
} |
350
|
|
|
$message .= "--{$this->get_boundary()}--" . self::$LINE_BREAK; |
351
|
|
|
} |
352
|
|
|
return $message; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Private holder for the boundry logic |
357
|
|
|
* Not called/created unless it's needed |
358
|
|
|
* |
359
|
|
|
* @return string boundary |
360
|
|
|
*/ |
361
|
|
|
private $boundary; |
362
|
|
|
private function get_boundary() |
363
|
|
|
{ |
364
|
|
|
if(!isset($this->boundary)) |
365
|
|
|
$this->boundary = sprintf(self::$BOUNDARY_FORMAT, md5(date('r', time()) . self::$BOUNDARY_SALT)); |
366
|
|
|
return $this->boundary; |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* Private holder for the alternative boundry logic |
371
|
|
|
* Not called/created unless it's needed |
372
|
|
|
* |
373
|
|
|
* @return string alternative boundary |
374
|
|
|
*/ |
375
|
|
|
private $alternative_boundary; |
376
|
|
|
private function get_alternative_boundary() |
377
|
|
|
{ |
378
|
|
|
if(!isset($this->alternative_boundary)) |
379
|
|
|
$this->alternative_boundary = sprintf(self::$ALTERNATIVE_BOUNDARY_FORMAT, md5(date('r', time()) . self::$ALTERNATIVE_BOUNDARY_SALT)); |
380
|
|
|
return $this->alternative_boundary; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Fetcher for the additional headers needed for multipart emails |
385
|
|
|
* |
386
|
|
|
* @return string headers needed for multipart |
387
|
|
|
*/ |
388
|
|
|
private function get_additional_headers() |
389
|
|
|
{ |
390
|
|
|
$headers = ''; |
391
|
|
|
foreach($this->header_array as $key => $value) |
|
|
|
|
392
|
|
|
{ |
393
|
|
|
$headers .= "{$key}: {$value}" . self::$LINE_BREAK; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
if(count($this->cc_array) > 0) |
397
|
|
|
$headers .= 'CC: ' . implode(', ', $this->cc_array) . self::$LINE_BREAK; |
|
|
|
|
398
|
|
|
if(count($this->bcc_array) > 0) |
|
|
|
|
399
|
|
|
$headers .= 'BCC: ' . implode(', ', $this->bcc_array) . self::$LINE_BREAK; |
|
|
|
|
400
|
|
|
|
401
|
|
|
if(isset($this->attachment_array) && count($this->attachment_array) > 0) |
402
|
|
|
$headers .= "Content-Type: multipart/mixed; boundary=\"{$this->get_boundary()}\""; |
403
|
|
|
else if( |
404
|
|
|
isset($this->plain_message) && strlen($this->plain_message) > 0 && |
405
|
|
|
isset($this->html_message) && strlen($this->html_message) > 0) |
406
|
|
|
{ |
407
|
|
|
$headers .= "Content-Type: multipart/alternative; boundary=\"{$this->get_alternative_boundary()}\""; |
408
|
|
|
} |
409
|
|
|
else if(isset($this->html_message) && strlen($this->html_message) > 0) |
410
|
|
|
$headers .= 'Content-type: text/html; charset="iso-8859-1"'; |
411
|
|
|
|
412
|
|
|
return $headers; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* File reader for attachments |
417
|
|
|
* |
418
|
|
|
* @return string binary representation of file, base64'd |
419
|
|
|
*/ |
420
|
|
|
private function get_attachment_content($attachment) |
421
|
|
|
{ |
422
|
|
|
$handle = fopen($attachment->path, 'r'); |
423
|
|
|
$contents = fread($handle, filesize($attachment->path)); |
424
|
|
|
fclose($handle); |
425
|
|
|
|
426
|
|
|
$contents = base64_encode($contents); |
427
|
|
|
$contents = chunk_split($contents); |
428
|
|
|
return $contents; |
429
|
|
|
} |
430
|
|
|
} |
431
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.