1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @package email-gateways |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* One of the two core email gateways. |
8
|
|
|
* Provides simple SMTP functionalities. |
9
|
|
|
* Supports AUTH LOGIN, SSL and TLS. |
10
|
|
|
* |
11
|
|
|
* @author Huib Keemink, Michael Eichelsdoerfer |
12
|
|
|
*/ |
13
|
|
|
class SMTPGateway extends EmailGateway |
14
|
|
|
{ |
15
|
|
|
protected $_SMTP; |
16
|
|
|
protected $_helo_hostname; |
17
|
|
|
protected $_host; |
18
|
|
|
protected $_port; |
19
|
|
|
protected $_protocol = 'tcp'; |
20
|
|
|
protected $_secure = 'no'; |
21
|
|
|
protected $_auth = false; |
22
|
|
|
protected $_user; |
23
|
|
|
protected $_pass; |
24
|
|
|
protected $_envelope_from; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Returns the name, used in the dropdown menu in the preferences pane. |
28
|
|
|
* |
29
|
|
|
* @return array |
30
|
|
|
*/ |
31
|
|
|
public static function about() |
32
|
|
|
{ |
33
|
|
|
return array( |
34
|
|
|
'name' => __('SMTP'), |
35
|
|
|
); |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Constructor. Sets basic default values based on preferences. |
40
|
|
|
* |
41
|
|
|
* @throws EmailValidationException |
42
|
|
|
*/ |
43
|
|
|
public function __construct() |
44
|
|
|
{ |
45
|
|
|
parent::__construct(); |
46
|
|
|
$this->setConfiguration(Symphony::Configuration()->get('email_smtp')); |
|
|
|
|
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Send an email using an SMTP server |
51
|
|
|
* |
52
|
|
|
* @throws EmailGatewayException |
53
|
|
|
* @throws EmailValidationException |
54
|
|
|
* @throws Exception |
55
|
|
|
* @return boolean |
56
|
|
|
*/ |
57
|
|
|
public function send() |
58
|
|
|
{ |
59
|
|
|
$this->validate(); |
60
|
|
|
|
61
|
|
|
$settings = array(); |
62
|
|
|
$settings['helo_hostname'] = $this->_helo_hostname; |
63
|
|
|
if ($this->_auth) { |
64
|
|
|
$settings['username'] = $this->_user; |
65
|
|
|
$settings['password'] = $this->_pass; |
66
|
|
|
} |
67
|
|
|
$settings['secure'] = $this->_secure; |
68
|
|
|
|
69
|
|
|
try { |
70
|
|
|
if (!is_a($this->_SMTP, 'SMTP')) { |
71
|
|
|
$this->_SMTP = new SMTP($this->_host, $this->_port, $settings); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
// Encode recipient names (but not any numeric array indexes) |
75
|
|
|
$recipients = array(); |
76
|
|
View Code Duplication |
foreach ($this->_recipients as $name => $email) { |
77
|
|
|
// Support Bcc header |
78
|
|
|
if (isset($this->_header_fields['Bcc']) && $this->_header_fields['Bcc'] === $email) { |
79
|
|
|
continue; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
// if the key is not numeric, qEncode the key. |
83
|
|
|
$name = General::intval($name) > -1 ? General::intval($name) : EmailHelper::qEncode($name); |
84
|
|
|
$recipients[$name] = $email; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
// Combine keys and values into a recipient list (name <email>, name <email>). |
88
|
|
|
$recipient_list = EmailHelper::arrayToList($recipients); |
89
|
|
|
|
90
|
|
|
// Encode the subject |
91
|
|
|
$subject = EmailHelper::qEncode((string)$this->_subject); |
92
|
|
|
|
93
|
|
|
// Build the 'From' header field body |
94
|
|
|
$from = empty($this->_sender_name) |
95
|
|
|
? $this->_sender_email_address |
96
|
|
|
: EmailHelper::qEncode($this->_sender_name) . ' <' . $this->_sender_email_address . '>'; |
97
|
|
|
|
98
|
|
|
// Build the 'Reply-To' header field body |
99
|
|
View Code Duplication |
if (!empty($this->_reply_to_email_address)) { |
100
|
|
|
$reply_to = empty($this->_reply_to_name) |
101
|
|
|
? $this->_reply_to_email_address |
102
|
|
|
: EmailHelper::qEncode($this->_reply_to_name) . ' <'.$this->_reply_to_email_address.'>'; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
if (!empty($reply_to)) { |
106
|
|
|
$this->_header_fields = array_merge( |
107
|
|
|
$this->_header_fields, |
108
|
|
|
array( |
109
|
|
|
'Reply-To' => $reply_to, |
110
|
|
|
) |
111
|
|
|
); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
// Build the body text using attachments, html-text and plain-text. |
115
|
|
|
$this->prepareMessageBody(); |
116
|
|
|
|
117
|
|
|
// Build the header fields |
118
|
|
|
$this->_header_fields = array_merge( |
119
|
|
|
$this->_header_fields, |
120
|
|
|
array( |
121
|
|
|
'Message-ID' => sprintf('<%s@%s>', md5(uniqid()), HTTP_HOST), |
122
|
|
|
'Date' => date('r'), |
123
|
|
|
'From' => $from, |
124
|
|
|
'Subject' => $subject, |
125
|
|
|
'To' => $recipient_list, |
126
|
|
|
'X-Mailer' => 'Symphony Email Module', |
127
|
|
|
'MIME-Version' => '1.0' |
128
|
|
|
) |
129
|
|
|
); |
130
|
|
|
|
131
|
|
|
// Set header fields and fold header field bodies |
132
|
|
|
foreach ($this->_header_fields as $name => $body) { |
133
|
|
|
$this->_SMTP->setHeader($name, EmailHelper::fold($body)); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
// Send the email command. If the envelope from variable is set, use that for the MAIL command. This improves bounce handling. |
137
|
|
|
$this->_SMTP->sendMail(is_null($this->_envelope_from)?$this->_sender_email_address:$this->_envelope_from, $this->_recipients, $this->_body); |
138
|
|
|
|
139
|
|
|
if ($this->_keepalive === false) { |
140
|
|
|
$this->closeConnection(); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
$this->reset(); |
144
|
|
|
} catch (SMTPException $e) { |
145
|
|
|
throw new EmailGatewayException($e->getMessage()); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
return true; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Resets the headers, body, subject |
153
|
|
|
* |
154
|
|
|
* @return void |
155
|
|
|
*/ |
156
|
|
|
public function reset() |
157
|
|
|
{ |
158
|
|
|
$this->_header_fields = array(); |
159
|
|
|
$this->_envelope_from = null; |
160
|
|
|
$this->_recipients = array(); |
161
|
|
|
$this->_subject = null; |
162
|
|
|
$this->_body = null; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
public function openConnection() |
166
|
|
|
{ |
167
|
|
|
return parent::openConnection(); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
public function closeConnection() |
171
|
|
|
{ |
172
|
|
|
if (is_a($this->_SMTP, 'SMTP')) { |
173
|
|
|
try { |
174
|
|
|
$this->_SMTP->quit(); |
175
|
|
|
return parent::closeConnection(); |
176
|
|
|
} catch (Exception $e) { |
177
|
|
|
} |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
parent::closeConnection(); |
181
|
|
|
return false; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Sets the HELO/EHLO hostanme |
186
|
|
|
* |
187
|
|
|
* @param string $helo_hostname |
188
|
|
|
* @return void |
189
|
|
|
*/ |
190
|
|
|
public function setHeloHostname($helo_hostname = null) |
191
|
|
|
{ |
192
|
|
|
$this->_helo_hostname = $helo_hostname; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Sets the host to connect to. |
197
|
|
|
* |
198
|
|
|
* @param null|string $host (optional) |
199
|
|
|
* @return void |
200
|
|
|
*/ |
201
|
|
|
public function setHost($host = null) |
202
|
|
|
{ |
203
|
|
|
if ($host === null) { |
204
|
|
|
$host = '127.0.0.1'; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
if (substr($host, 0, 6) === 'ssl://') { |
208
|
|
|
$this->_protocol = 'ssl'; |
209
|
|
|
$this->_secure = 'ssl'; |
210
|
|
|
$host = substr($host, 6); |
211
|
|
|
} |
212
|
|
|
$this->_host = $host; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Sets the port, used in the connection. |
217
|
|
|
* |
218
|
|
|
* @param null|int $port |
219
|
|
|
* @return void |
220
|
|
|
*/ |
221
|
|
|
public function setPort($port = null) |
222
|
|
|
{ |
223
|
|
|
if (is_null($port)) { |
224
|
|
|
$port = ($this->_protocol === 'ssl') ? 465 : 25; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
$this->_port = $port; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Sets the username to use with AUTH LOGIN |
232
|
|
|
* |
233
|
|
|
* @param string $user |
234
|
|
|
* @return void |
235
|
|
|
*/ |
236
|
|
|
public function setUser($user = null) |
237
|
|
|
{ |
238
|
|
|
$this->_user = $user; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Sets the password to use with AUTH LOGIN |
243
|
|
|
* |
244
|
|
|
* @param string $pass |
245
|
|
|
* @return void |
246
|
|
|
*/ |
247
|
|
|
public function setPass($pass = null) |
248
|
|
|
{ |
249
|
|
|
$this->_pass = $pass; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Use AUTH login or no auth. |
254
|
|
|
* |
255
|
|
|
* @param boolean $auth |
256
|
|
|
* @return void |
257
|
|
|
*/ |
258
|
|
|
public function setAuth($auth = false) |
259
|
|
|
{ |
260
|
|
|
$this->_auth = $auth; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Sets the encryption used. |
265
|
|
|
* |
266
|
|
|
* @param string $secure |
267
|
|
|
* The encryption used. Can be 'ssl', 'tls'. Anything else defaults to |
268
|
|
|
* a non secure TCP connection |
269
|
|
|
* @return void |
270
|
|
|
*/ |
271
|
|
|
public function setSecure($secure = null) |
272
|
|
|
{ |
273
|
|
|
if ($secure === 'tls') { |
274
|
|
|
$this->_protocol = 'tcp'; |
275
|
|
|
$this->_secure = 'tls'; |
276
|
|
|
} elseif ($secure === 'ssl') { |
277
|
|
|
$this->_protocol = 'ssl'; |
278
|
|
|
$this->_secure = 'ssl'; |
279
|
|
|
} else { |
280
|
|
|
$this->_protocol = 'tcp'; |
281
|
|
|
$this->_secure = 'no'; |
282
|
|
|
} |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Sets the envelope_from address. This is only available via the API, as it is an expert-only feature. |
287
|
|
|
* |
288
|
|
|
* @since 2.3.1 |
289
|
|
|
* @param null $envelope_from |
290
|
|
|
* @throws EmailValidationException |
291
|
|
|
* @return void |
292
|
|
|
*/ |
293
|
|
|
public function setEnvelopeFrom($envelope_from = null) |
294
|
|
|
{ |
295
|
|
|
if (preg_match('%[\r\n]%', $envelope_from)) { |
296
|
|
|
throw new EmailValidationException(__('The Envelope From Address can not contain carriage return or newlines.')); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
$this->_envelope_from = $envelope_from; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Sets all configuration entries from an array. |
304
|
|
|
* |
305
|
|
|
* @param array $config |
306
|
|
|
* All configuration entries stored in a single array. |
307
|
|
|
* The array should have the format of the $_POST array created by the preferences HTML. |
308
|
|
|
* @throws EmailValidationException |
309
|
|
|
* @since 2.3.1 |
310
|
|
|
* @return void |
311
|
|
|
*/ |
312
|
|
|
public function setConfiguration($config) |
313
|
|
|
{ |
314
|
|
|
$this->setHeloHostname($config['helo_hostname']); |
315
|
|
|
$this->setFrom($config['from_address'], $config['from_name']); |
316
|
|
|
$this->setHost($config['host']); |
317
|
|
|
$this->setPort($config['port']); |
318
|
|
|
$this->setSecure($config['secure']); |
319
|
|
|
|
320
|
|
|
if ($config['auth'] === 1) { |
321
|
|
|
$this->setAuth(true); |
322
|
|
|
$this->setUser($config['username']); |
323
|
|
|
$this->setPass($config['password']); |
324
|
|
|
} else { |
325
|
|
|
$this->setAuth(false); |
326
|
|
|
$this->setUser(''); |
327
|
|
|
$this->setPass(''); |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* Builds the preferences pane, shown in the Symphony backend. |
333
|
|
|
* |
334
|
|
|
* @throws InvalidArgumentException |
335
|
|
|
* @return XMLElement |
336
|
|
|
*/ |
337
|
|
|
public function getPreferencesPane() |
338
|
|
|
{ |
339
|
|
|
parent::getPreferencesPane(); |
340
|
|
|
$group = new XMLElement('fieldset'); |
341
|
|
|
$group->setAttribute('class', 'settings condensed pickable'); |
342
|
|
|
$group->setAttribute('id', 'smtp'); |
343
|
|
|
$group->appendChild(new XMLElement('legend', __('Email: SMTP'))); |
344
|
|
|
|
345
|
|
|
$div = new XMLElement('div'); |
346
|
|
|
|
347
|
|
|
$readonly = array('readonly' => 'readonly'); |
348
|
|
|
|
349
|
|
|
$label = Widget::Label(__('HELO Hostname')); |
350
|
|
|
$label->appendChild(Widget::Input('settings[email_smtp][helo_hostname]', $this->_helo_hostname, 'text', $readonly)); |
351
|
|
|
$div->appendChild($label); |
352
|
|
|
|
353
|
|
|
$group->appendChild($div); |
354
|
|
|
$group->appendChild(new XMLElement('p', __('A fully qualified domain name (FQDN) of your server, e.g. "www.example.com". If left empty, Symphony will attempt to find an IP address for the EHLO/HELO greeting.'), array('class' => 'help'))); |
355
|
|
|
|
356
|
|
|
$div = new XMLElement('div'); |
357
|
|
|
$div->setAttribute('class', 'two columns'); |
358
|
|
|
|
359
|
|
|
$label = Widget::Label(__('From Name')); |
360
|
|
|
$label->setAttribute('class', 'column'); |
361
|
|
|
$label->appendChild(Widget::Input('settings[email_smtp][from_name]', $this->_sender_name, 'text', $readonly)); |
362
|
|
|
$div->appendChild($label); |
363
|
|
|
|
364
|
|
|
$label = Widget::Label(__('From Email Address')); |
365
|
|
|
$label->setAttribute('class', 'column'); |
366
|
|
|
$label->appendChild(Widget::Input('settings[email_smtp][from_address]', $this->_sender_email_address, 'text', $readonly)); |
367
|
|
|
$div->appendChild($label); |
368
|
|
|
|
369
|
|
|
$group->appendChild($div); |
370
|
|
|
|
371
|
|
|
$div = new XMLElement('div'); |
372
|
|
|
$div->setAttribute('class', 'two columns'); |
373
|
|
|
|
374
|
|
|
$label = Widget::Label(__('Host')); |
375
|
|
|
$label->setAttribute('class', 'column'); |
376
|
|
|
$label->appendChild(Widget::Input('settings[email_smtp][host]', $this->_host, 'text', $readonly)); |
377
|
|
|
$div->appendChild($label); |
378
|
|
|
|
379
|
|
|
$label = Widget::Label(__('Port')); |
380
|
|
|
$label->setAttribute('class', 'column'); |
381
|
|
|
$label->appendChild(Widget::Input('settings[email_smtp][port]', (string)$this->_port, 'text', $readonly)); |
382
|
|
|
$div->appendChild($label); |
383
|
|
|
$group->appendChild($div); |
384
|
|
|
|
385
|
|
|
$label = Widget::Label(); |
386
|
|
|
$label->setAttribute('class', 'column'); |
387
|
|
|
// To fix the issue with checkboxes that do not send a value when unchecked. |
388
|
|
|
$options = array( |
389
|
|
|
array('no',$this->_secure === 'no', __('No encryption')), |
390
|
|
|
array('ssl',$this->_secure === 'ssl', __('SSL encryption')), |
391
|
|
|
array('tls',$this->_secure === 'tls', __('TLS encryption')), |
392
|
|
|
); |
393
|
|
|
$select = Widget::Select('settings[email_smtp][secure]', $options, $readonly); |
394
|
|
|
$label->appendChild($select); |
395
|
|
|
$group->appendChild($label); |
396
|
|
|
|
397
|
|
|
$group->appendChild(new XMLElement('p', __('For a secure connection, SSL and TLS are supported. Please check the manual of your email provider for more details.'), array('class' => 'help'))); |
398
|
|
|
|
399
|
|
|
$label = Widget::Label(); |
400
|
|
|
$label->setAttribute('class', 'column'); |
401
|
|
|
// To fix the issue with checkboxes that do not send a value when unchecked. |
402
|
|
|
$group->appendChild(Widget::Input('settings[email_smtp][auth]', '0', 'hidden')); |
403
|
|
|
$input = Widget::Input('settings[email_smtp][auth]', '1', 'checkbox', $readonly); |
404
|
|
|
|
405
|
|
|
if ($this->_auth === true) { |
406
|
|
|
$input->setAttribute('checked', 'checked'); |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
$label->setValue(__('%s Requires authentication', array($input->generate()))); |
410
|
|
|
$group->appendChild($label); |
411
|
|
|
|
412
|
|
|
$group->appendChild(new XMLElement('p', __('Some SMTP connections require authentication. If that is the case, enter the username/password combination below.'), array('class' => 'help'))); |
413
|
|
|
|
414
|
|
|
$div = new XMLElement('div'); |
415
|
|
|
$div->setAttribute('class', 'two columns'); |
416
|
|
|
|
417
|
|
|
$label = Widget::Label(__('Username')); |
418
|
|
|
$label->setAttribute('class', 'column'); |
419
|
|
|
$label->appendChild(Widget::Input('settings[email_smtp][username]', $this->_user, 'text', array_merge($readonly, array('autocomplete' => 'off')))); |
420
|
|
|
$div->appendChild($label); |
421
|
|
|
|
422
|
|
|
$label = Widget::Label(__('Password')); |
423
|
|
|
$label->setAttribute('class', 'column'); |
424
|
|
|
$label->appendChild(Widget::Input('settings[email_smtp][password]', $this->_pass, 'password', array_merge($readonly, array('autocomplete' => 'off')))); |
425
|
|
|
$div->appendChild($label); |
426
|
|
|
$group->appendChild($div); |
427
|
|
|
|
428
|
|
|
return $group; |
429
|
|
|
} |
430
|
|
|
} |
431
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.