1 | <?php |
||||||
2 | |||||||
3 | /** |
||||||
4 | * @package toolkit |
||||||
5 | */ |
||||||
6 | |||||||
7 | /** |
||||||
8 | * The standard exception to be thrown by all email gateways. |
||||||
9 | */ |
||||||
10 | class EmailGatewayException extends Exception |
||||||
11 | { |
||||||
12 | /** |
||||||
13 | * Creates a new exception, and logs the error. |
||||||
14 | * |
||||||
15 | * @param string $message |
||||||
16 | * @param integer $code |
||||||
17 | * @param Exception $previous |
||||||
18 | * The previous exception, if nested. See |
||||||
19 | * http://www.php.net/manual/en/language.exceptions.extending.php |
||||||
20 | */ |
||||||
21 | public function __construct($message, $code = 0, $previous = null) |
||||||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||||||
22 | { |
||||||
23 | $trace = $this->getTrace(); |
||||||
24 | // Best-guess to retrieve classname of email-gateway. |
||||||
25 | // Might fail in non-standard uses, will then return an |
||||||
26 | // empty string. |
||||||
27 | $gateway_class = $trace[1]['class']?' (' . $trace[1]['class'] . ')':''; |
||||||
0 ignored issues
–
show
|
|||||||
28 | Symphony::Log()->pushToLog(__('Email Gateway Error') . $gateway_class . ': ' . $message, $code, true); |
||||||
29 | |||||||
30 | // CDATA the $message: Do not trust input from others |
||||||
31 | $message = General::wrapInCDATA(trim($message)); |
||||||
32 | parent::__construct($message); |
||||||
33 | } |
||||||
34 | } |
||||||
35 | |||||||
36 | /** |
||||||
37 | * The validation exception to be thrown by all email gateways. |
||||||
38 | * This exception is thrown if data does not pass validation. |
||||||
39 | */ |
||||||
40 | class EmailValidationException extends EmailGatewayException |
||||||
41 | { |
||||||
42 | } |
||||||
43 | |||||||
44 | /** |
||||||
45 | * A base class for email gateways. |
||||||
46 | * All email-gateways should extend this class in order to work. |
||||||
47 | * |
||||||
48 | * @todo add validation to all set functions. |
||||||
49 | */ |
||||||
50 | abstract class EmailGateway |
||||||
51 | { |
||||||
52 | protected $_recipients = array(); |
||||||
53 | protected $_sender_name; |
||||||
54 | protected $_sender_email_address; |
||||||
55 | protected $_subject; |
||||||
56 | protected $_body; |
||||||
57 | protected $_text_plain; |
||||||
58 | protected $_text_html; |
||||||
59 | protected $_attachments = array(); |
||||||
60 | protected $_validate_attachment_errors = true; |
||||||
61 | protected $_reply_to_name; |
||||||
62 | protected $_reply_to_email_address; |
||||||
63 | protected $_header_fields = array(); |
||||||
64 | protected $_boundary_mixed; |
||||||
65 | protected $_boundary_alter; |
||||||
66 | protected $_text_encoding = 'quoted-printable'; |
||||||
67 | |||||||
68 | /** |
||||||
69 | * Indicates whether the connection to the SMTP server should be |
||||||
70 | * kept alive, or closed after sending each email. Defaults to false. |
||||||
71 | * |
||||||
72 | * @since Symphony 2.3.1 |
||||||
73 | * @var boolean |
||||||
74 | */ |
||||||
75 | protected $_keepalive = false; |
||||||
76 | |||||||
77 | /** |
||||||
78 | * The constructor sets the `_boundary_mixed` and `_boundary_alter` variables |
||||||
79 | * to be unique hashes based off PHP's `uniqid` function. |
||||||
80 | */ |
||||||
81 | public function __construct() |
||||||
82 | { |
||||||
83 | $this->_boundary_mixed = '=_mix_'.md5(uniqid()); |
||||||
84 | $this->_boundary_alter = '=_alt_'.md5(uniqid()); |
||||||
85 | } |
||||||
86 | |||||||
87 | /** |
||||||
88 | * The destructor ensures that any open connections to the Email Gateway |
||||||
89 | * is closed. |
||||||
90 | */ |
||||||
91 | public function __destruct() |
||||||
92 | { |
||||||
93 | $this->closeConnection(); |
||||||
94 | } |
||||||
95 | |||||||
96 | /** |
||||||
97 | * Sends the actual email. This function should be implemented in the |
||||||
98 | * Email Gateway itself and should return true or false if the email |
||||||
99 | * was successfully sent. |
||||||
100 | * See the default gateway for an example. |
||||||
101 | * |
||||||
102 | * @return boolean |
||||||
103 | */ |
||||||
104 | abstract public function send(); |
||||||
105 | |||||||
106 | /** |
||||||
107 | * Open new connection to the email server. |
||||||
108 | * This function is used to allow persistent connections. |
||||||
109 | * |
||||||
110 | * @since Symphony 2.3.1 |
||||||
111 | * @return boolean |
||||||
112 | */ |
||||||
113 | public function openConnection() |
||||||
114 | { |
||||||
115 | $this->_keepalive = true; |
||||||
116 | return true; |
||||||
117 | } |
||||||
118 | |||||||
119 | /** |
||||||
120 | * Close the connection to the email Server. |
||||||
121 | * This function is used to allow persistent connections. |
||||||
122 | * |
||||||
123 | * @since Symphony 2.3.1 |
||||||
124 | * @return boolean |
||||||
125 | */ |
||||||
126 | public function closeConnection() |
||||||
127 | { |
||||||
128 | $this->_keepalive = false; |
||||||
129 | return true; |
||||||
130 | } |
||||||
131 | |||||||
132 | /** |
||||||
133 | * Sets the sender-email and sender-name. |
||||||
134 | * |
||||||
135 | * @param string $email |
||||||
136 | * The email-address emails will be sent from. |
||||||
137 | * @param string $name |
||||||
138 | * The name the emails will be sent from. |
||||||
139 | * @throws EmailValidationException |
||||||
140 | * @return void |
||||||
141 | */ |
||||||
142 | public function setFrom($email, $name) |
||||||
143 | { |
||||||
144 | $this->setSenderEmailAddress($email); |
||||||
145 | $this->setSenderName($name); |
||||||
146 | } |
||||||
147 | |||||||
148 | /** |
||||||
149 | * Does some basic checks to validate the |
||||||
150 | * value of a header field. Currently only checks |
||||||
151 | * if the value contains a carriage return or a new line. |
||||||
152 | * |
||||||
153 | * @param string $value |
||||||
154 | * |
||||||
155 | * @return boolean |
||||||
156 | */ |
||||||
157 | protected function validateHeaderFieldValue($value) |
||||||
158 | { |
||||||
159 | // values can't contains carriage returns or new lines |
||||||
160 | $carriage_returns = preg_match('%[\r\n]%', $value); |
||||||
161 | |||||||
162 | return !$carriage_returns; |
||||||
163 | } |
||||||
164 | |||||||
165 | /** |
||||||
166 | * Sets the sender-email. |
||||||
167 | * |
||||||
168 | * @throws EmailValidationException |
||||||
169 | * @param string $email |
||||||
170 | * The email-address emails will be sent from. |
||||||
171 | * @return void |
||||||
172 | */ |
||||||
173 | public function setSenderEmailAddress($email) |
||||||
174 | { |
||||||
175 | if (!$this->validateHeaderFieldValue($email)) { |
||||||
176 | throw new EmailValidationException(__('Sender Email Address can not contain carriage return or newlines.')); |
||||||
177 | } |
||||||
178 | |||||||
179 | $this->_sender_email_address = $email; |
||||||
180 | } |
||||||
181 | |||||||
182 | /** |
||||||
183 | * Sets the sender-name. |
||||||
184 | * |
||||||
185 | * @throws EmailValidationException |
||||||
186 | * @param string $name |
||||||
187 | * The name emails will be sent from. |
||||||
188 | * @return void |
||||||
189 | */ |
||||||
190 | public function setSenderName($name) |
||||||
191 | { |
||||||
192 | if (!$this->validateHeaderFieldValue($name)) { |
||||||
193 | throw new EmailValidationException(__('Sender Name can not contain carriage return or newlines.')); |
||||||
194 | } |
||||||
195 | |||||||
196 | $this->_sender_name = $name; |
||||||
197 | } |
||||||
198 | |||||||
199 | /** |
||||||
200 | * Sets the recipients. |
||||||
201 | * |
||||||
202 | * @param string|array $email |
||||||
203 | * The email-address(es) to send the email to. |
||||||
204 | * @throws EmailValidationException |
||||||
205 | * @return void |
||||||
206 | */ |
||||||
207 | public function setRecipients($email) |
||||||
208 | { |
||||||
209 | if (!is_array($email)) { |
||||||
210 | $email = explode(',', $email); |
||||||
211 | // trim all values |
||||||
212 | array_walk($email, function(&$val) { |
||||||
0 ignored issues
–
show
|
|||||||
213 | return $val = trim($val); |
||||||
214 | }); |
||||||
215 | // remove empty elements |
||||||
216 | $email = array_filter($email); |
||||||
217 | } |
||||||
218 | |||||||
219 | foreach ($email as $e) { |
||||||
220 | if (!$this->validateHeaderFieldValue($e)) { |
||||||
221 | throw new EmailValidationException(__('Recipient address can not contain carriage return or newlines.')); |
||||||
222 | } |
||||||
223 | } |
||||||
224 | |||||||
225 | $this->_recipients = $email; |
||||||
226 | } |
||||||
227 | |||||||
228 | /** |
||||||
229 | * This functions takes a string to be used as the plaintext |
||||||
230 | * content for the Email |
||||||
231 | * |
||||||
232 | * @todo sanitizing and security checking |
||||||
233 | * @param string $text_plain |
||||||
234 | */ |
||||||
235 | public function setTextPlain($text_plain) |
||||||
236 | { |
||||||
237 | $this->_text_plain = $text_plain; |
||||||
238 | } |
||||||
239 | |||||||
240 | /** |
||||||
241 | * This functions takes a string to be used as the HTML |
||||||
242 | * content for the Email |
||||||
243 | * |
||||||
244 | * @todo sanitizing and security checking |
||||||
245 | * @param string $text_html |
||||||
246 | */ |
||||||
247 | public function setTextHtml($text_html) |
||||||
248 | { |
||||||
249 | $this->_text_html = $text_html; |
||||||
250 | } |
||||||
251 | |||||||
252 | /** |
||||||
253 | * This function sets one or multiple attachment files |
||||||
254 | * to the email. |
||||||
255 | * |
||||||
256 | * Passing `null` to this function will |
||||||
257 | * erase the current values with an empty array. |
||||||
258 | * |
||||||
259 | * @param string|array $files |
||||||
260 | * Accepts the same parameters format as `EmailGateway::addAttachment()` |
||||||
261 | * but you can also all multiple values at once if all files are |
||||||
262 | * wrap in a array. |
||||||
263 | * |
||||||
264 | * Example: |
||||||
265 | * ```` |
||||||
266 | * $email->setAttachments(array( |
||||||
267 | * array( |
||||||
268 | * 'file' => 'http://example.com/foo.txt', |
||||||
269 | * 'charset' => 'UTF-8' |
||||||
270 | * ), |
||||||
271 | * 'path/to/your/webspace/foo/bar.csv', |
||||||
272 | * ... |
||||||
273 | * )); |
||||||
274 | * ```` |
||||||
275 | */ |
||||||
276 | public function setAttachments($files) |
||||||
277 | { |
||||||
278 | // Always erase |
||||||
279 | $this->_attachments = array(); |
||||||
280 | |||||||
281 | // check if we have an input value |
||||||
282 | if ($files == null) { |
||||||
283 | return; |
||||||
284 | } |
||||||
285 | |||||||
286 | // make sure we are dealing with an array |
||||||
287 | if (!is_array($files)) { |
||||||
288 | $files = array($files); |
||||||
289 | } |
||||||
290 | |||||||
291 | // Append each attachment one by one in order |
||||||
292 | // to normalize each input |
||||||
293 | foreach ($files as $key => $file) { |
||||||
294 | if (is_numeric($key)) { |
||||||
295 | // key is numeric, assume keyed array or string |
||||||
296 | $this->appendAttachment($file); |
||||||
297 | } else { |
||||||
298 | // key is not numeric, assume key is filename |
||||||
299 | // and file is a string, key needs to be preserved |
||||||
300 | $this->appendAttachment(array($key => $file)); |
||||||
301 | } |
||||||
302 | } |
||||||
303 | } |
||||||
304 | |||||||
305 | /** |
||||||
306 | * Appends one file attachment to the attachments array. |
||||||
307 | * |
||||||
308 | * @since Symphony 2.3.5 |
||||||
309 | * |
||||||
310 | * @param string|array $file |
||||||
311 | * Can be a string representing the file path, absolute or relative, i.e. |
||||||
312 | * `'http://example.com/foo.txt'` or `'path/to/your/webspace/foo/bar.csv'`. |
||||||
313 | * |
||||||
314 | * Can also be a keyed array. This will enable more options, like setting the |
||||||
315 | * charset used by mail agent to open the file or a different filename. |
||||||
316 | * |
||||||
317 | * Example: |
||||||
318 | * ```` |
||||||
319 | * $email->appendAttachment(array( |
||||||
320 | * 'file' => 'http://example.com/foo.txt', |
||||||
321 | * 'filename' => 'bar.txt', |
||||||
322 | * 'charset' => 'UTF-8', |
||||||
323 | * 'mime-type' => 'text/csv', |
||||||
324 | * )); |
||||||
325 | * ```` |
||||||
326 | */ |
||||||
327 | public function appendAttachment($file) |
||||||
328 | { |
||||||
329 | if (!is_array($file)) { |
||||||
330 | // treat the param as string (old format) |
||||||
331 | $file = array( |
||||||
332 | 'file' => $file, |
||||||
333 | 'filename' => null, |
||||||
334 | 'charset' => null, |
||||||
335 | ); |
||||||
336 | |||||||
337 | // is array, but not the right key |
||||||
338 | } elseif (!isset($file['file'])) { |
||||||
339 | // another (un-documented) old format: key is filename |
||||||
340 | $keys = array_keys($file); |
||||||
341 | $file = array( |
||||||
342 | 'file' => $file[$keys[0]], |
||||||
343 | 'filename' => is_numeric($keys[0]) ? null : $keys[0], |
||||||
344 | 'charset' => null, |
||||||
345 | ); |
||||||
346 | } |
||||||
347 | |||||||
348 | // push properly formatted file entry |
||||||
349 | $this->_attachments[] = $file; |
||||||
350 | } |
||||||
351 | |||||||
352 | /** |
||||||
353 | * Sets the property `$_validate_attachment_errors` |
||||||
354 | * |
||||||
355 | * This property is true by default, so sending will break if any attachment |
||||||
356 | * can not be loaded; if it is false, attachment errors error will be ignored. |
||||||
357 | * |
||||||
358 | * @since Symphony 2.7 |
||||||
359 | * @param boolean $validate_attachment_errors |
||||||
360 | * @return void |
||||||
361 | */ |
||||||
362 | public function setValidateAttachmentErrors($validate_attachment_errors) |
||||||
363 | { |
||||||
364 | if (!is_bool($validate_attachment_errors)) { |
||||||
0 ignored issues
–
show
|
|||||||
365 | throw new EmailGatewayException(__('%s accepts boolean values only.', array('<code>setValidateAttachmentErrors</code>'))); |
||||||
366 | } else { |
||||||
367 | $this->_validate_attachment_errors = $validate_attachment_errors; |
||||||
368 | } |
||||||
369 | } |
||||||
370 | |||||||
371 | /** |
||||||
372 | * @todo Document this function |
||||||
373 | * @throws EmailGatewayException |
||||||
374 | * @param string $encoding |
||||||
375 | * Must be either `quoted-printable` or `base64`. |
||||||
376 | */ |
||||||
377 | public function setTextEncoding($encoding = null) |
||||||
0 ignored issues
–
show
|
|||||||
378 | { |
||||||
379 | if ($encoding == 'quoted-printable') { |
||||||
380 | $this->_text_encoding = 'quoted-printable'; |
||||||
381 | } elseif ($encoding == 'base64') { |
||||||
382 | $this->_text_encoding = 'base64'; |
||||||
383 | } elseif (!$encoding) { |
||||||
384 | $this->_text_encoding = false; |
||||||
385 | } else { |
||||||
386 | throw new EmailGatewayException(__('%1$s is not a supported encoding type. Please use %2$s or %3$s. You can also use %4$s for no encoding.', array($encoding, '<code>quoted-printable</code>', '<code>base-64</code>', '<code>false</code>'))); |
||||||
387 | } |
||||||
388 | } |
||||||
389 | |||||||
390 | /** |
||||||
391 | * Sets the subject. |
||||||
392 | * |
||||||
393 | * @param string $subject |
||||||
394 | * The subject that the email will have. |
||||||
395 | * @return void |
||||||
396 | */ |
||||||
397 | public function setSubject($subject) |
||||||
398 | { |
||||||
399 | //TODO: sanitizing and security checking; |
||||||
400 | $this->_subject = $subject; |
||||||
401 | } |
||||||
402 | |||||||
403 | /** |
||||||
404 | * Sets the reply-to-email. |
||||||
405 | * |
||||||
406 | * @throws EmailValidationException |
||||||
407 | * @param string $email |
||||||
408 | * The email-address emails should be replied to. |
||||||
409 | * @return void |
||||||
410 | */ |
||||||
411 | public function setReplyToEmailAddress($email) |
||||||
412 | { |
||||||
413 | if (preg_match('%[\r\n]%', $email)) { |
||||||
414 | throw new EmailValidationException(__('Reply-To Email Address can not contain carriage return or newlines.')); |
||||||
415 | } |
||||||
416 | |||||||
417 | $this->_reply_to_email_address = $email; |
||||||
418 | } |
||||||
419 | |||||||
420 | /** |
||||||
421 | * Sets the reply-to-name. |
||||||
422 | * |
||||||
423 | * @throws EmailValidationException |
||||||
424 | * @param string $name |
||||||
425 | * The name emails should be replied to. |
||||||
426 | * @return void |
||||||
427 | */ |
||||||
428 | public function setReplyToName($name) |
||||||
429 | { |
||||||
430 | if (preg_match('%[\r\n]%', $name)) { |
||||||
431 | throw new EmailValidationException(__('Reply-To Name can not contain carriage return or newlines.')); |
||||||
432 | } |
||||||
433 | |||||||
434 | $this->_reply_to_name = $name; |
||||||
435 | } |
||||||
436 | |||||||
437 | /** |
||||||
438 | * Sets all configuration entries from an array. |
||||||
439 | * This enables extensions like the ENM to create email settings panes that work regardless of the email gateway. |
||||||
440 | * Every gateway should extend this method to add their own settings. |
||||||
441 | * |
||||||
442 | * @throws EmailValidationException |
||||||
443 | * @param array $config |
||||||
444 | * @since Symphony 2.3.1 |
||||||
445 | * All configuration entries stored in a single array. The array should have the format of the $_POST array created by the preferences HTML. |
||||||
446 | * @return boolean |
||||||
447 | */ |
||||||
448 | public function setConfiguration($config) |
||||||
449 | { |
||||||
450 | return true; |
||||||
451 | } |
||||||
452 | |||||||
453 | /** |
||||||
454 | * Appends a single header field to the header fields array. |
||||||
455 | * The header field should be presented as a name/body pair. |
||||||
456 | * |
||||||
457 | * @throws EmailGatewayException |
||||||
458 | * @param string $name |
||||||
459 | * The header field name. Examples are From, Bcc, X-Sender and Reply-to. |
||||||
460 | * @param string $body |
||||||
461 | * The header field body. |
||||||
462 | * @return void |
||||||
463 | */ |
||||||
464 | public function appendHeaderField($name, $body) |
||||||
465 | { |
||||||
466 | if (is_array($body)) { |
||||||
0 ignored issues
–
show
|
|||||||
467 | throw new EmailGatewayException(__('%s accepts strings only; arrays are not allowed.', array('<code>appendHeaderField</code>'))); |
||||||
468 | } |
||||||
469 | |||||||
470 | $this->_header_fields[$name] = $body; |
||||||
471 | } |
||||||
472 | |||||||
473 | /** |
||||||
474 | * Appends one or more header fields to the header fields array. |
||||||
475 | * Header fields should be presented as an array with name/body pairs. |
||||||
476 | * |
||||||
477 | * @param array $header_array |
||||||
478 | * The header fields. Examples are From, X-Sender and Reply-to. |
||||||
479 | * @throws EmailGatewayException |
||||||
480 | * @return void |
||||||
481 | */ |
||||||
482 | public function appendHeaderFields(array $header_array = array()) |
||||||
0 ignored issues
–
show
|
|||||||
483 | { |
||||||
484 | foreach ($header_array as $name => $body) { |
||||||
485 | $this->appendHeaderField($name, $body); |
||||||
486 | } |
||||||
487 | } |
||||||
488 | |||||||
489 | /** |
||||||
490 | * Makes sure the Subject, Sender Email and Recipients values are |
||||||
491 | * all set and are valid. The message body is checked in |
||||||
492 | * `prepareMessageBody` |
||||||
493 | * |
||||||
494 | * @see prepareMessageBody() |
||||||
495 | * @throws EmailValidationException |
||||||
496 | * @return boolean |
||||||
497 | */ |
||||||
498 | public function validate() |
||||||
499 | { |
||||||
500 | if (strlen(trim($this->_subject)) <= 0) { |
||||||
501 | throw new EmailValidationException(__('Email subject cannot be empty.')); |
||||||
502 | } elseif (strlen(trim($this->_sender_email_address)) <= 0) { |
||||||
503 | throw new EmailValidationException(__('Sender email address cannot be empty.')); |
||||||
504 | } elseif (!filter_var($this->_sender_email_address, FILTER_VALIDATE_EMAIL)) { |
||||||
505 | throw new EmailValidationException(__('Sender email address must be a valid email address.')); |
||||||
506 | } else { |
||||||
507 | foreach ($this->_recipients as $address) { |
||||||
508 | if (strlen(trim($address)) <= 0) { |
||||||
509 | throw new EmailValidationException(__('Recipient email address cannot be empty.')); |
||||||
510 | } elseif (!filter_var($address, FILTER_VALIDATE_EMAIL)) { |
||||||
511 | throw new EmailValidationException(__('The email address ‘%s’ is invalid.', array($address))); |
||||||
512 | } |
||||||
513 | } |
||||||
514 | } |
||||||
515 | |||||||
516 | return true; |
||||||
517 | } |
||||||
518 | |||||||
519 | /** |
||||||
520 | * Build the message body and the content-describing header fields |
||||||
521 | * |
||||||
522 | * The result of this building is an updated body variable in the |
||||||
523 | * gateway itself. |
||||||
524 | * |
||||||
525 | * @throws EmailGatewayException |
||||||
526 | * @throws Exception |
||||||
527 | * @return boolean |
||||||
528 | */ |
||||||
529 | protected function prepareMessageBody() |
||||||
530 | { |
||||||
531 | $attachments = $this->getSectionAttachments(); |
||||||
532 | if ($attachments) { |
||||||
533 | $this->appendHeaderFields($this->contentInfoArray('multipart/mixed')); |
||||||
534 | if (!empty($this->_text_plain) && !empty($this->_text_html)) { |
||||||
535 | $this->_body = $this->boundaryDelimiterLine('multipart/mixed') |
||||||
536 | . $this->contentInfoString('multipart/alternative') |
||||||
537 | . $this->getSectionMultipartAlternative() |
||||||
538 | . $attachments |
||||||
539 | ; |
||||||
0 ignored issues
–
show
|
|||||||
540 | } elseif (!empty($this->_text_plain)) { |
||||||
541 | $this->_body = $this->boundaryDelimiterLine('multipart/mixed') |
||||||
542 | . $this->contentInfoString('text/plain') |
||||||
543 | . $this->getSectionTextPlain() |
||||||
544 | . $attachments |
||||||
545 | ; |
||||||
0 ignored issues
–
show
|
|||||||
546 | } elseif (!empty($this->_text_html)) { |
||||||
547 | $this->_body = $this->boundaryDelimiterLine('multipart/mixed') |
||||||
548 | . $this->contentInfoString('text/html') |
||||||
549 | . $this->getSectionTextHtml() |
||||||
550 | . $attachments |
||||||
551 | ; |
||||||
0 ignored issues
–
show
|
|||||||
552 | } else { |
||||||
553 | $this->_body = $attachments; |
||||||
554 | } |
||||||
0 ignored issues
–
show
|
|||||||
555 | $this->_body .= $this->finalBoundaryDelimiterLine('multipart/mixed'); |
||||||
556 | } elseif (!empty($this->_text_plain) && !empty($this->_text_html)) { |
||||||
557 | $this->appendHeaderFields($this->contentInfoArray('multipart/alternative')); |
||||||
558 | $this->_body = $this->getSectionMultipartAlternative(); |
||||||
559 | } elseif (!empty($this->_text_plain)) { |
||||||
560 | $this->appendHeaderFields($this->contentInfoArray('text/plain')); |
||||||
561 | $this->_body = $this->getSectionTextPlain(); |
||||||
562 | } elseif (!empty($this->_text_html)) { |
||||||
563 | $this->appendHeaderFields($this->contentInfoArray('text/html')); |
||||||
564 | $this->_body = $this->getSectionTextHtml(); |
||||||
565 | } else { |
||||||
566 | throw new EmailGatewayException(__('No attachments or body text was set. Can not send empty email.')); |
||||||
567 | } |
||||||
568 | } |
||||||
569 | |||||||
570 | /** |
||||||
571 | * Build multipart email section. Used by sendmail and smtp classes to |
||||||
572 | * send multipart email. |
||||||
573 | * |
||||||
574 | * Will return a string containing the section. Can be used to send to |
||||||
575 | * an email server directly. |
||||||
576 | * @return string |
||||||
577 | */ |
||||||
578 | protected function getSectionMultipartAlternative() |
||||||
579 | { |
||||||
580 | $output = $this->boundaryDelimiterLine('multipart/alternative') |
||||||
581 | . $this->contentInfoString('text/plain') |
||||||
582 | . $this->getSectionTextPlain() |
||||||
583 | . $this->boundaryDelimiterLine('multipart/alternative') |
||||||
584 | . $this->contentInfoString('text/html') |
||||||
585 | . $this->getSectionTextHtml() |
||||||
586 | . $this->finalBoundaryDelimiterLine('multipart/alternative') |
||||||
0 ignored issues
–
show
|
|||||||
587 | ; |
||||||
0 ignored issues
–
show
|
|||||||
588 | |||||||
589 | return $output; |
||||||
590 | } |
||||||
591 | |||||||
592 | /** |
||||||
593 | * Builds the attachment section of a multipart email. |
||||||
594 | * |
||||||
595 | * Will return a string containing the section. Can be used to send to |
||||||
596 | * an email server directly. |
||||||
597 | * |
||||||
598 | * @throws EmailGatewayException |
||||||
599 | * @throws Exception |
||||||
600 | * @return string |
||||||
601 | */ |
||||||
602 | protected function getSectionAttachments() |
||||||
603 | { |
||||||
604 | $output = ''; |
||||||
605 | |||||||
606 | foreach ($this->_attachments as $key => $file) { |
||||||
607 | $tmp_file = false; |
||||||
608 | |||||||
609 | // If the attachment is a URL, download the file to a temporary location. |
||||||
610 | // This prevents downloading the file twice - once for info, once for data. |
||||||
611 | if (filter_var($file['file'], FILTER_VALIDATE_URL)) { |
||||||
612 | $gateway = new Gateway(); |
||||||
613 | $gateway->init($file['file']); |
||||||
614 | $gateway->setopt('TIMEOUT', 30); |
||||||
615 | $file_content = @$gateway->exec(); |
||||||
616 | |||||||
617 | $tmp_file = tempnam(TMP, 'attachment'); |
||||||
0 ignored issues
–
show
|
|||||||
618 | General::writeFile($tmp_file, $file_content, Symphony::Configuration()->get('write_mode', 'file')); |
||||||
0 ignored issues
–
show
It seems like
Symphony::Configuration(...t('write_mode', 'file') can also be of type array ; however, parameter $perm of General::writeFile() does only seem to accept integer|string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
619 | |||||||
620 | $original_filename = $file['file']; |
||||||
621 | $file['file'] = $tmp_file; |
||||||
622 | |||||||
623 | // Without this the temporary filename will be used. Ugly! |
||||||
624 | if (is_null($file['filename'])) { |
||||||
625 | $file['filename'] = basename($original_filename); |
||||||
626 | } |
||||||
627 | } else { |
||||||
628 | $file_content = @file_get_contents($file['file']); |
||||||
629 | } |
||||||
630 | |||||||
631 | if ($file_content !== false && !empty($file_content)) { |
||||||
632 | $output .= $this->boundaryDelimiterLine('multipart/mixed') |
||||||
633 | . $this->contentInfoString($file['mime-type'], $file['file'], $file['filename'], $file['charset']) |
||||||
634 | . EmailHelper::base64ContentTransferEncode($file_content); |
||||||
0 ignored issues
–
show
It seems like
$file_content can also be of type true ; however, parameter $data of EmailHelper::base64ContentTransferEncode() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
635 | } else { |
||||||
636 | if ($this->_validate_attachment_errors) { |
||||||
637 | if (!$tmp_file === false) { |
||||||
638 | $filename = $original_filename; |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
639 | } else { |
||||||
640 | $filename = $file['file']; |
||||||
641 | } |
||||||
642 | |||||||
643 | throw new EmailGatewayException(__('The content of the file `%s` could not be loaded.', array($filename))); |
||||||
644 | } |
||||||
645 | } |
||||||
646 | |||||||
647 | if (!$tmp_file === false) { |
||||||
648 | General::deleteFile($tmp_file); |
||||||
0 ignored issues
–
show
It seems like
$tmp_file can also be of type false ; however, parameter $file of General::deleteFile() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
649 | } |
||||||
650 | } |
||||||
0 ignored issues
–
show
|
|||||||
651 | return $output; |
||||||
652 | } |
||||||
653 | |||||||
654 | /** |
||||||
655 | * Builds the text section of a text/plain email. |
||||||
656 | * |
||||||
657 | * Will return a string containing the section. Can be used to send to |
||||||
658 | * an email server directly. |
||||||
659 | * @return string |
||||||
660 | */ |
||||||
661 | protected function getSectionTextPlain() |
||||||
662 | { |
||||||
663 | if ($this->_text_encoding == 'quoted-printable') { |
||||||
664 | return EmailHelper::qpContentTransferEncode($this->_text_plain)."\r\n"; |
||||||
665 | } elseif ($this->_text_encoding == 'base64') { |
||||||
666 | // don't add CRLF if using base64 - spam filters don't |
||||||
667 | // like this |
||||||
668 | return EmailHelper::base64ContentTransferEncode($this->_text_plain); |
||||||
669 | } |
||||||
670 | |||||||
671 | return $this->_text_plain."\r\n"; |
||||||
672 | } |
||||||
673 | |||||||
674 | /** |
||||||
675 | * Builds the html section of a text/html email. |
||||||
676 | * |
||||||
677 | * Will return a string containing the section. Can be used to send to |
||||||
678 | * an email server directly. |
||||||
679 | * @return string |
||||||
680 | */ |
||||||
681 | protected function getSectionTextHtml() |
||||||
682 | { |
||||||
683 | if ($this->_text_encoding == 'quoted-printable') { |
||||||
684 | return EmailHelper::qpContentTransferEncode($this->_text_html)."\r\n"; |
||||||
685 | } elseif ($this->_text_encoding == 'base64') { |
||||||
686 | // don't add CRLF if using base64 - spam filters don't |
||||||
687 | // like this |
||||||
688 | return EmailHelper::base64ContentTransferEncode($this->_text_html); |
||||||
689 | } |
||||||
0 ignored issues
–
show
|
|||||||
690 | return $this->_text_html."\r\n"; |
||||||
691 | } |
||||||
692 | |||||||
693 | /** |
||||||
694 | * Builds the right content-type/encoding types based on file and |
||||||
695 | * content-type. |
||||||
696 | * |
||||||
697 | * Will try to match a common description, based on the $type param. |
||||||
698 | * If nothing is found, will return a base64 attached file disposition. |
||||||
699 | * |
||||||
700 | * Can be used to send to an email server directly. |
||||||
701 | * |
||||||
702 | * @param string $type optional mime-type |
||||||
703 | * @param string $file optional the path of the attachment |
||||||
704 | * @param string $filename optional the name of the attached file |
||||||
705 | * @param string $charset optional the charset of the attached file |
||||||
706 | * |
||||||
707 | * @return array |
||||||
708 | */ |
||||||
709 | public function contentInfoArray($type = null, $file = null, $filename = null, $charset = null) |
||||||
0 ignored issues
–
show
|
|||||||
710 | { |
||||||
711 | // Common descriptions |
||||||
712 | $description = array( |
||||||
713 | 'multipart/mixed' => array( |
||||||
714 | 'Content-Type' => 'multipart/mixed; boundary="' |
||||||
715 | .$this->getBoundary('multipart/mixed').'"', |
||||||
716 | ), |
||||||
717 | 'multipart/alternative' => array( |
||||||
718 | 'Content-Type' => 'multipart/alternative; boundary="' |
||||||
719 | .$this->getBoundary('multipart/alternative').'"', |
||||||
720 | ), |
||||||
721 | 'text/plain' => array( |
||||||
722 | 'Content-Type' => 'text/plain; charset=UTF-8', |
||||||
723 | 'Content-Transfer-Encoding' => $this->_text_encoding ? $this->_text_encoding : '8bit', |
||||||
0 ignored issues
–
show
|
|||||||
724 | ), |
||||||
725 | 'text/html' => array( |
||||||
726 | 'Content-Type' => 'text/html; charset=UTF-8', |
||||||
727 | 'Content-Transfer-Encoding' => $this->_text_encoding ? $this->_text_encoding : '8bit', |
||||||
0 ignored issues
–
show
|
|||||||
728 | ), |
||||||
729 | ); |
||||||
730 | |||||||
731 | // Try common |
||||||
732 | if (!empty($type) && !empty($description[$type])) { |
||||||
733 | // return it if found |
||||||
734 | return $description[$type]; |
||||||
735 | } |
||||||
736 | |||||||
737 | // assure we have a file name |
||||||
738 | $filename = !is_null($filename) ? $filename : basename($file); |
||||||
739 | |||||||
740 | // Format charset for insertion in content-type, if needed |
||||||
741 | if (!empty($charset)) { |
||||||
742 | $charset = sprintf('charset=%s;', $charset); |
||||||
743 | } else { |
||||||
744 | $charset = ''; |
||||||
745 | } |
||||||
0 ignored issues
–
show
|
|||||||
746 | // if the mime type is not set, try to obtain using the getMimeType |
||||||
747 | if (empty($type)) { |
||||||
748 | //assume that the attachment mimetime is appended |
||||||
749 | $type = General::getMimeType($file); |
||||||
0 ignored issues
–
show
The method
General::getMimeType() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
750 | } |
||||||
0 ignored issues
–
show
|
|||||||
751 | // Return binary description |
||||||
752 | return array( |
||||||
753 | 'Content-Type' => $type.';'.$charset.' name="'.$filename.'"', |
||||||
754 | 'Content-Transfer-Encoding' => 'base64', |
||||||
755 | 'Content-Disposition' => 'attachment; filename="' .$filename .'"', |
||||||
756 | ); |
||||||
757 | } |
||||||
758 | |||||||
759 | /** |
||||||
760 | * Creates the properly formatted InfoString based on the InfoArray. |
||||||
761 | * |
||||||
762 | * @see EmailGateway::contentInfoArray() |
||||||
763 | * |
||||||
764 | * @return string|null |
||||||
765 | */ |
||||||
766 | protected function contentInfoString($type = null, $file = null, $filename = null, $charset = null) |
||||||
0 ignored issues
–
show
|
|||||||
767 | { |
||||||
768 | $data = $this->contentInfoArray($type, $file, $filename, $charset); |
||||||
769 | $fields = array(); |
||||||
770 | |||||||
771 | foreach ($data as $key => $value) { |
||||||
772 | $fields[] = EmailHelper::fold(sprintf('%s: %s', $key, $value)); |
||||||
773 | } |
||||||
774 | |||||||
775 | return !empty($fields) ? implode("\r\n", $fields)."\r\n\r\n" : null; |
||||||
776 | } |
||||||
777 | |||||||
778 | /** |
||||||
779 | * Returns the bondary based on the $type parameter |
||||||
780 | * |
||||||
781 | * @param string $type the multipart type |
||||||
782 | * @return string|void |
||||||
783 | */ |
||||||
784 | protected function getBoundary($type) |
||||||
785 | { |
||||||
786 | switch ($type) { |
||||||
787 | case 'multipart/mixed': |
||||||
788 | return $this->_boundary_mixed; |
||||||
789 | case 'multipart/alternative': |
||||||
790 | return $this->_boundary_alter; |
||||||
791 | } |
||||||
792 | } |
||||||
793 | |||||||
794 | /** |
||||||
795 | * @param string $type |
||||||
796 | * @return string |
||||||
797 | */ |
||||||
798 | protected function boundaryDelimiterLine($type) |
||||||
799 | { |
||||||
800 | // As requested by RFC 2046: 'The CRLF preceding the boundary |
||||||
801 | // delimiter line is conceptually attached to the boundary.' |
||||||
802 | return $this->getBoundary($type) ? "\r\n--".$this->getBoundary($type)."\r\n" : null; |
||||||
803 | } |
||||||
804 | |||||||
805 | /** |
||||||
806 | * @param string $type |
||||||
807 | * @return string |
||||||
808 | */ |
||||||
809 | protected function finalBoundaryDelimiterLine($type) |
||||||
810 | { |
||||||
811 | return $this->getBoundary($type) ? "\r\n--".$this->getBoundary($type)."--\r\n" : null; |
||||||
812 | } |
||||||
813 | |||||||
814 | /** |
||||||
815 | * Sets a property. |
||||||
816 | * |
||||||
817 | * Magic function, supplied by php. |
||||||
818 | * This function will try and find a method of this class, by |
||||||
819 | * camelcasing the name, and appending it with set. |
||||||
820 | * If the function can not be found, an exception will be thrown. |
||||||
821 | * |
||||||
822 | * @param string $name |
||||||
823 | * The property name. |
||||||
824 | * @param string $value |
||||||
825 | * The property value; |
||||||
826 | * @throws EmailGatewayException |
||||||
827 | * @return void|boolean |
||||||
828 | */ |
||||||
829 | public function __set($name, $value) |
||||||
830 | { |
||||||
831 | if (method_exists(get_class($this), 'set'.$this->__toCamel($name, true))) { |
||||||
832 | return $this->{'set'.$this->__toCamel($name, true)}($value); |
||||||
833 | } else { |
||||||
834 | throw new EmailGatewayException(__('The %1$s gateway does not support the use of %2$s', array(get_class($this), $name))); |
||||||
835 | } |
||||||
836 | } |
||||||
837 | |||||||
838 | /** |
||||||
839 | * Gets a property. |
||||||
840 | * |
||||||
841 | * Magic function, supplied by php. |
||||||
842 | * This function will attempt to find a variable set with `$name` and |
||||||
843 | * returns it. If the variable is not set, it will return false. |
||||||
844 | * |
||||||
845 | * @since Symphony 2.2.2 |
||||||
846 | * @param string $name |
||||||
847 | * The property name. |
||||||
848 | * @return boolean|mixed |
||||||
849 | */ |
||||||
850 | public function __get($name) |
||||||
851 | { |
||||||
852 | return isset($this->{'_'.$name}) ? $this->{'_'.$name} : false; |
||||||
853 | } |
||||||
854 | |||||||
855 | /** |
||||||
856 | * The preferences to add to the preferences pane in the admin area. |
||||||
857 | * |
||||||
858 | * @return XMLElement |
||||||
859 | */ |
||||||
860 | public function getPreferencesPane() |
||||||
861 | { |
||||||
862 | return new XMLElement('fieldset'); |
||||||
863 | } |
||||||
864 | |||||||
865 | /** |
||||||
866 | * Internal function to turn underscored variables into camelcase, for |
||||||
867 | * use in methods. |
||||||
868 | * Because Symphony has a difference in naming between properties and |
||||||
869 | * methods (underscored vs camelcased) and the Email class uses the |
||||||
870 | * magic __set function to find property-setting-methods, this |
||||||
871 | * conversion is needed. |
||||||
872 | * |
||||||
873 | * @param string $string |
||||||
874 | * The string to convert |
||||||
875 | * @param boolean $caseFirst |
||||||
876 | * If this is true, the first character will be uppercased. Useful |
||||||
877 | * for method names (setName). |
||||||
878 | * If set to false, the first character will be lowercased. This is |
||||||
879 | * default behaviour. |
||||||
880 | * @return string |
||||||
881 | */ |
||||||
882 | private function __toCamel($string, $caseFirst = false) |
||||||
0 ignored issues
–
show
|
|||||||
883 | { |
||||||
884 | $string = strtolower($string); |
||||||
885 | $a = explode('_', $string); |
||||||
886 | $a = array_map('ucfirst', $a); |
||||||
887 | |||||||
888 | if (!$caseFirst) { |
||||||
889 | $a[0] = lcfirst($a[0]); |
||||||
890 | } |
||||||
891 | |||||||
892 | return implode('', $a); |
||||||
893 | } |
||||||
894 | |||||||
895 | /** |
||||||
896 | * The reverse of the __toCamel function. |
||||||
897 | * |
||||||
898 | * @param string $string |
||||||
899 | * The string to convert |
||||||
900 | * @return string |
||||||
901 | */ |
||||||
902 | private function __fromCamel($string) |
||||||
903 | { |
||||||
904 | $string[0] = strtolower($string[0]); |
||||||
905 | |||||||
906 | return preg_replace_callback('/([A-Z])/', function($c) { |
||||||
0 ignored issues
–
show
|
|||||||
907 | return "_" . strtolower($c[1]); |
||||||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
The string literal
_ does not require double quotes, as per coding-style, please use single quotes.
PHP provides two ways to mark string literals. Either with single quotes String literals in single quotes on the other hand are evaluated very literally and the only two
characters that needs escaping in the literal are the single quote itself ( Double quoted string literals may contain other variables or more complex escape sequences. <?php
$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";
print $doubleQuoted;
will print an indented: If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear. For more information on PHP string literals and available escape sequences see the PHP core documentation. ![]() |
|||||||
908 | }, $string); |
||||||
909 | } |
||||||
910 | } |
||||||
911 |