GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — master (#2835)
by
unknown
06:01
created

EmailGateway::appendHeaderField()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 7
rs 9.4285
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)
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'] . ')':'';
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) {
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. It deletes all previously attached files.
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 3.0.0
309
     *   The file array can contain a 'cid' key.
310
     *   When set to true, the Content-ID header field is added with the filename as id.
311
     *   The file array can contain a 'disposition' key.
312
     *   When set, it is used in the Content-Disposition header
313
     * @throws EmailGatewayException if the parameter is not valid.
314
     *
315
     * @since Symphony 2.3.5
316
     *
317
     * @param string|array $file
318
     *   Can be a string representing the file path, absolute or relative, i.e.
319
     *   `'http://example.com/foo.txt'` or `'path/to/your/webspace/foo/bar.csv'`.
320
     *
321
     *   Can also be a keyed array. This will enable more options, like setting the
322
     *   charset used by mail agent to open the file or a different filename.
323
     *   Only the "file" key is required.
324
     *
325
     *   Example:
326
     *   ````
327
     *   $email->appendAttachment(array(
328
     *      'file' => 'http://example.com/foo.txt',
329
     *      'filename' => 'bar.txt',
330
     *      'charset' => 'UTF-8',
331
     *      'mime-type' => 'text/csv',
332
     *      'cid' => false,
333
     *      'disposition' => 'inline',
334
     *   ));
335
     *   ````
336
     */
337
    public function appendAttachment($file)
338
    {
339
        if (!is_array($file)) {
340
            // treat the param as string
341
            $file = array(
342
                'file' => $file,
343
            );
344
345
            // is array, but not the right key
346
        } elseif (!isset($file['file'])) {
347
            throw new EmailGatewayException('The file key is missing from the attachment array.');
348
        }
349
350
        // push properly formatted file entry
351
        $this->_attachments[] = $file;
352
    }
353
354
    /**
355
     * Sets the property `$_validate_attachment_errors`
356
     *
357
     * This property is true by default, so sending will break if any attachment
358
     * can not be loaded; if it is false, attachment errors error will be ignored.
359
     *
360
     * @since Symphony 2.7
361
     * @param boolean $validate_attachment_errors
362
     * @return void
363
     */
364
    public function setValidateAttachmentErrors($validate_attachment_errors)
365
    {
366
        if (!is_bool($validate_attachment_errors)) {
0 ignored issues
show
introduced by
The condition is_bool($validate_attachment_errors) is always true.
Loading history...
367
            throw new EmailGatewayException(__('%s accepts boolean values only.', array('<code>setValidateAttachmentErrors</code>')));
368
        } else {
369
            $this->_validate_attachment_errors = $validate_attachment_errors;
370
        }
371
    }
372
373
    /**
374
     * @todo Document this function
375
     * @throws EmailGatewayException
376
     * @param string $encoding
377
     *  Must be either `quoted-printable` or `base64`.
378
     */
379
    public function setTextEncoding($encoding = null)
380
    {
381
        if ($encoding == 'quoted-printable') {
382
            $this->_text_encoding = 'quoted-printable';
383
        } elseif ($encoding == 'base64') {
384
            $this->_text_encoding = 'base64';
385
        } elseif (!$encoding) {
386
            $this->_text_encoding = false;
387
        } else {
388
            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>')));
389
        }
390
    }
391
392
    /**
393
     * Sets the subject.
394
     *
395
     * @param string $subject
396
     *  The subject that the email will have.
397
     * @return void
398
     */
399
    public function setSubject($subject)
400
    {
401
        //TODO: sanitizing and security checking;
402
        $this->_subject = $subject;
403
    }
404
405
    /**
406
     * Sets the reply-to-email.
407
     *
408
     * @throws EmailValidationException
409
     * @param string $email
410
     *  The email-address emails should be replied to.
411
     * @return void
412
     */
413
    public function setReplyToEmailAddress($email)
414
    {
415
        if (preg_match('%[\r\n]%', $email)) {
416
            throw new EmailValidationException(__('Reply-To Email Address can not contain carriage return or newlines.'));
417
        }
418
419
        $this->_reply_to_email_address = $email;
420
    }
421
422
    /**
423
     * Sets the reply-to-name.
424
     *
425
     * @throws EmailValidationException
426
     * @param string $name
427
     *  The name emails should be replied to.
428
     * @return void
429
     */
430
    public function setReplyToName($name)
431
    {
432
        if (preg_match('%[\r\n]%', $name)) {
433
            throw new EmailValidationException(__('Reply-To Name can not contain carriage return or newlines.'));
434
        }
435
436
        $this->_reply_to_name = $name;
437
    }
438
439
    /**
440
     * Sets all configuration entries from an array.
441
     * This enables extensions like the ENM to create email settings panes that work regardless of the email gateway.
442
     * Every gateway should extend this method to add their own settings.
443
     *
444
     * @throws EmailValidationException
445
     * @param array $config
446
     * @since Symphony 2.3.1
447
     *  All configuration entries stored in a single array. The array should have the format of the $_POST array created by the preferences HTML.
448
     * @return boolean
449
     */
450
    public function setConfiguration($config)
451
    {
452
        return true;
453
    }
454
455
    /**
456
     * Appends a single header field to the header fields array.
457
     * The header field should be presented as a name/body pair.
458
     *
459
     * @throws EmailGatewayException
460
     * @param string $name
461
     *  The header field name. Examples are From, Bcc, X-Sender and Reply-to.
462
     * @param string $body
463
     *  The header field body.
464
     * @return void
465
     */
466
    public function appendHeaderField($name, $body)
467
    {
468
        if (is_array($body)) {
0 ignored issues
show
introduced by
The condition is_array($body) is always false.
Loading history...
469
            throw new EmailGatewayException(__('%s accepts strings only; arrays are not allowed.', array('<code>appendHeaderField</code>')));
470
        }
471
472
        $this->_header_fields[$name] = $body;
473
    }
474
475
    /**
476
     * Appends one or more header fields to the header fields array.
477
     * Header fields should be presented as an array with name/body pairs.
478
     *
479
     * @param array $header_array
480
     *  The header fields. Examples are From, X-Sender and Reply-to.
481
     * @throws EmailGatewayException
482
     * @return void
483
     */
484
    public function appendHeaderFields(array $header_array = array())
485
    {
486
        foreach ($header_array as $name => $body) {
487
            $this->appendHeaderField($name, $body);
488
        }
489
    }
490
491
    /**
492
     * Makes sure the Subject, Sender Email and Recipients values are
493
     * all set and are valid. The message body is checked in
494
     * `prepareMessageBody`
495
     *
496
     * @see prepareMessageBody()
497
     * @throws EmailValidationException
498
     * @return boolean
499
     */
500
    public function validate()
501
    {
502
        if (strlen(trim($this->_subject)) <= 0) {
503
            throw new EmailValidationException(__('Email subject cannot be empty.'));
504
        } elseif (strlen(trim($this->_sender_email_address)) <= 0) {
505
            throw new EmailValidationException(__('Sender email address cannot be empty.'));
506
        } elseif (!filter_var($this->_sender_email_address, FILTER_VALIDATE_EMAIL)) {
507
            throw new EmailValidationException(__('Sender email address must be a valid email address.'));
508
        } else {
509
            foreach ($this->_recipients as $address) {
510
                if (strlen(trim($address)) <= 0) {
511
                    throw new EmailValidationException(__('Recipient email address cannot be empty.'));
512
                } elseif (!filter_var($address, FILTER_VALIDATE_EMAIL)) {
513
                    throw new EmailValidationException(__('The email address ‘%s’ is invalid.', array($address)));
514
                }
515
            }
516
        }
517
518
        return true;
519
    }
520
521
    /**
522
     * Build the message body and the content-describing header fields
523
     *
524
     * The result of this building is an updated body variable in the
525
     * gateway itself.
526
     *
527
     * @throws EmailGatewayException
528
     * @throws Exception
529
     * @return boolean
530
     */
531
    protected function prepareMessageBody()
532
    {
533
        $attachments = $this->getSectionAttachments();
534
        if ($attachments) {
535
            $this->appendHeaderFields($this->contentInfoArray('multipart/mixed'));
536
            if (!empty($this->_text_plain) && !empty($this->_text_html)) {
537
                $this->_body = $this->boundaryDelimiterLine('multipart/mixed')
538
                            . $this->contentInfoString('multipart/alternative')
539
                            . $this->getSectionMultipartAlternative()
540
                            . $attachments
541
                ;
542
            } elseif (!empty($this->_text_plain)) {
543
                $this->_body = $this->boundaryDelimiterLine('multipart/mixed')
544
                            . $this->contentInfoString('text/plain')
545
                            . $this->getSectionTextPlain()
546
                            . $attachments
547
                ;
548
            } elseif (!empty($this->_text_html)) {
549
                $this->_body = $this->boundaryDelimiterLine('multipart/mixed')
550
                            . $this->contentInfoString('text/html')
551
                            . $this->getSectionTextHtml()
552
                            . $attachments
553
                ;
554
            } else {
555
                $this->_body = $attachments;
556
            }
557
            $this->_body .= $this->finalBoundaryDelimiterLine('multipart/mixed');
558
        } elseif (!empty($this->_text_plain) && !empty($this->_text_html)) {
559
            $this->appendHeaderFields($this->contentInfoArray('multipart/alternative'));
560
            $this->_body = $this->getSectionMultipartAlternative();
561
        } elseif (!empty($this->_text_plain)) {
562
            $this->appendHeaderFields($this->contentInfoArray('text/plain'));
563
            $this->_body = $this->getSectionTextPlain();
564
        } elseif (!empty($this->_text_html)) {
565
            $this->appendHeaderFields($this->contentInfoArray('text/html'));
566
            $this->_body = $this->getSectionTextHtml();
567
        } else {
568
            throw new EmailGatewayException(__('No attachments or body text was set. Can not send empty email.'));
569
        }
570
    }
571
572
    /**
573
     * Build multipart email section. Used by sendmail and smtp classes to
574
     * send multipart email.
575
     *
576
     * Will return a string containing the section. Can be used to send to
577
     * an email server directly.
578
     * @return string
579
     */
580
    protected function getSectionMultipartAlternative()
581
    {
582
        $output = $this->boundaryDelimiterLine('multipart/alternative')
583
                . $this->contentInfoString('text/plain')
584
                . $this->getSectionTextPlain()
585
                . $this->boundaryDelimiterLine('multipart/alternative')
586
                . $this->contentInfoString('text/html')
587
                . $this->getSectionTextHtml()
588
                . $this->finalBoundaryDelimiterLine('multipart/alternative')
589
        ;
590
591
        return $output;
592
    }
593
594
    /**
595
     * Builds the attachment section of a multipart email.
596
     *
597
     * Will return a string containing the section. Can be used to send to
598
     * an email server directly.
599
     *
600
     * @throws EmailGatewayException
601
     * @throws Exception
602
     * @return string
603
     */
604
    protected function getSectionAttachments()
605
    {
606
        $output = '';
607
608
        foreach ($this->_attachments as $key => $file) {
609
            $tmp_file = false;
610
611
            // If the attachment is a URL, download the file to a temporary location.
612
            // This prevents downloading the file twice - once for info, once for data.
613
            if (filter_var($file['file'], FILTER_VALIDATE_URL)) {
614
                $gateway = new Gateway();
615
                $gateway->init($file['file']);
616
                $gateway->setopt('TIMEOUT', 30);
617
                $file_content = @$gateway->exec();
618
619
                $tmp_file = tempnam(TMP, 'attachment');
0 ignored issues
show
Bug introduced by
The constant TMP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
620
                General::writeFile($tmp_file, $file_content, Symphony::Configuration()->get('write_mode', 'file'));
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

620
                General::writeFile($tmp_file, $file_content, /** @scrutinizer ignore-type */ Symphony::Configuration()->get('write_mode', 'file'));
Loading history...
621
622
                $original_filename = $file['file'];
623
                $file['file'] = $tmp_file;
624
625
                // Without this the temporary filename will be used. Ugly!
626
                if (is_null($file['filename'])) {
627
                    $file['filename'] = basename($original_filename);
628
                }
629
            } else {
630
                $file_content = @file_get_contents($file['file']);
631
            }
632
633
            if ($file_content !== false && !empty($file_content)) {
634
                $output .= $this->boundaryDelimiterLine('multipart/mixed')
635
                     . $this->contentInfoString(
636
                            isset($file['mime-type']) ? $file['mime-type'] : null,
637
                            $file['file'],
638
                            isset($file['filename']) ? $file['filename'] : null,
639
                            isset($file['charset']) ? $file['charset'] : null,
640
                            isset($file['cid']) ? $file['cid'] : null,
641
                            isset($file['disposition']) ? $file['disposition'] : 'attachment'
642
                        )
643
                     . EmailHelper::base64ContentTransferEncode($file_content);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

643
                     . EmailHelper::base64ContentTransferEncode(/** @scrutinizer ignore-type */ $file_content);
Loading history...
644
            } else {
645
                if ($this->_validate_attachment_errors) {
646
                    if (!$tmp_file === false) {
647
                        $filename = $original_filename;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $original_filename does not seem to be defined for all execution paths leading up to this point.
Loading history...
648
                    } else {
649
                        $filename = $file['file'];
650
                    }
651
652
                    throw new EmailGatewayException(__('The content of the file `%s` could not be loaded.', array($filename)));
653
                }
654
            }
655
656
            if (!$tmp_file === false) {
657
                General::deleteFile($tmp_file);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

657
                General::deleteFile(/** @scrutinizer ignore-type */ $tmp_file);
Loading history...
658
            }
659
        }
660
        return $output;
661
    }
662
663
    /**
664
     * Builds the text section of a text/plain email.
665
     *
666
     * Will return a string containing the section. Can be used to send to
667
     * an email server directly.
668
     * @return string
669
     */
670
    protected function getSectionTextPlain()
671
    {
672
        if ($this->_text_encoding == 'quoted-printable') {
673
            return EmailHelper::qpContentTransferEncode($this->_text_plain)."\r\n";
674
        } elseif ($this->_text_encoding == 'base64') {
675
            // don't add CRLF if using base64 - spam filters don't
676
            // like this
677
            return EmailHelper::base64ContentTransferEncode($this->_text_plain);
678
        }
679
680
        return $this->_text_plain."\r\n";
681
    }
682
683
    /**
684
     * Builds the html section of a text/html email.
685
     *
686
     * Will return a string containing the section. Can be used to send to
687
     * an email server directly.
688
     * @return string
689
     */
690
    protected function getSectionTextHtml()
691
    {
692
        if ($this->_text_encoding == 'quoted-printable') {
693
            return EmailHelper::qpContentTransferEncode($this->_text_html)."\r\n";
694
        } elseif ($this->_text_encoding == 'base64') {
695
            // don't add CRLF if using base64 - spam filters don't
696
            // like this
697
            return EmailHelper::base64ContentTransferEncode($this->_text_html);
698
        }
699
        return $this->_text_html."\r\n";
700
    }
701
702
    /**
703
     * Builds the right content-type/encoding types based on file and
704
     * content-type.
705
     *
706
     * Will try to match a common description, based on the $type param.
707
     * If nothing is found, will return a base64 attached file disposition.
708
     *
709
     * Can be used to send to an email server directly.
710
     *
711
     * @param string $type optional mime-type
712
     * @param string $file optional the path of the attachment
713
     * @param string $filename optional the name of the attached file
714
     * @param string $charset optional the charset of the attached file
715
     * @param string|boolean $cid optional add a Content-ID header field. If true, uses the filename as the cid
716
     * @param string $disposition optional the value of the Content-Disposition header field
717
     *
718
     * @return array
719
     */
720
    public function contentInfoArray($type = null, $file = null, $filename = null, $charset = null, $cid = false, $disposition = 'attachment')
721
    {
722
        // Common descriptions
723
        $description = array(
724
            'multipart/mixed' => array(
725
                'Content-Type' => 'multipart/mixed; boundary="'
726
                                  .$this->getBoundary('multipart/mixed').'"',
727
            ),
728
            'multipart/alternative' => array(
729
                'Content-Type' => 'multipart/alternative; boundary="'
730
                                  .$this->getBoundary('multipart/alternative').'"',
731
            ),
732
            'text/plain' => array(
733
                'Content-Type'              => 'text/plain; charset=UTF-8',
734
                'Content-Transfer-Encoding' => $this->_text_encoding ? $this->_text_encoding : '8bit',
735
            ),
736
            'text/html' => array(
737
                'Content-Type'              => 'text/html; charset=UTF-8',
738
                'Content-Transfer-Encoding' => $this->_text_encoding ? $this->_text_encoding : '8bit',
739
            ),
740
        );
741
742
        // Try common
743
        if (!empty($type) && !empty($description[$type])) {
744
            // return it if found
745
            return $description[$type];
746
        }
747
748
        // assure we have a file name
749
        $filename = !is_null($filename) ? $filename : basename($file);
750
751
        // Format charset for insertion in content-type, if needed
752
        if (!empty($charset)) {
753
            $charset = sprintf('charset=%s;', $charset);
754
        } else {
755
            $charset = '';
756
        }
757
        // if the mime type is not set, try to obtain using the getMimeType
758
        if (empty($type)) {
759
            //assume that the attachment mimetime is appended
760
            $type = General::getMimeType($file);
0 ignored issues
show
Bug Best Practice introduced by
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 ignore-call  annotation

760
            /** @scrutinizer ignore-call */ 
761
            $type = General::getMimeType($file);
Loading history...
761
        }
762
        // Return binary description
763
        $bin = [
764
            'Content-Type'              => $type.';'.$charset.' name="'.$filename.'"',
765
            'Content-Transfer-Encoding' => 'base64',
766
        ];
767
        if ($disposition) {
768
            $bin['Content-Disposition'] = $disposition . '; filename="' .$filename .'"';
769
        }
770
        if ($cid) {
771
            $bin['Content-ID'] = $cid === true ? "<$filename>" : $cid;
772
        }
773
        return $bin;
774
    }
775
776
    /**
777
     * Creates the properly formatted InfoString based on the InfoArray.
778
     *
779
     * @see EmailGateway::contentInfoArray()
780
     *
781
     * @return string|null
782
     */
783
    protected function contentInfoString($type = null, $file = null, $filename = null, $charset = null, $cid = false, $disposition = 'attachment')
784
    {
785
        $data = $this->contentInfoArray($type, $file, $filename, $charset, $cid, $disposition);
786
        $fields = array();
787
788
        foreach ($data as $key => $value) {
789
            $fields[] = EmailHelper::fold(sprintf('%s: %s', $key, $value));
790
        }
791
792
        return !empty($fields) ? implode("\r\n", $fields)."\r\n\r\n" : null;
793
    }
794
795
    /**
796
     * Returns the bondary based on the $type parameter
797
     *
798
     * @param string $type the multipart type
799
     * @return string|void
800
     */
801
    protected function getBoundary($type)
802
    {
803
        switch ($type) {
804
            case 'multipart/mixed':
805
                return $this->_boundary_mixed;
806
            case 'multipart/alternative':
807
                return $this->_boundary_alter;
808
        }
809
    }
810
811
    /**
812
     * @param string $type
813
     * @return string
814
     */
815
    protected function boundaryDelimiterLine($type)
816
    {
817
        // As requested by RFC 2046: 'The CRLF preceding the boundary
818
        // delimiter line is conceptually attached to the boundary.'
819
        return $this->getBoundary($type) ? "\r\n--".$this->getBoundary($type)."\r\n" : null;
820
    }
821
822
    /**
823
     * @param string $type
824
     * @return string
825
     */
826
    protected function finalBoundaryDelimiterLine($type)
827
    {
828
        return $this->getBoundary($type) ? "\r\n--".$this->getBoundary($type)."--\r\n" : null;
829
    }
830
831
    /**
832
     * Sets a property.
833
     *
834
     * Magic function, supplied by php.
835
     * This function will try and find a method of this class, by
836
     * camelcasing the name, and appending it with set.
837
     * If the function can not be found, an exception will be thrown.
838
     *
839
     * @param string $name
840
     *  The property name.
841
     * @param string $value
842
     *  The property value;
843
     * @throws EmailGatewayException
844
     * @return void|boolean
845
     */
846
    public function __set($name, $value)
847
    {
848
        if (method_exists(get_class($this), 'set'.$this->__toCamel($name, true))) {
849
            return $this->{'set'.$this->__toCamel($name, true)}($value);
850
        } else {
851
            throw new EmailGatewayException(__('The %1$s gateway does not support the use of %2$s', array(get_class($this), $name)));
852
        }
853
    }
854
855
    /**
856
     * Gets a property.
857
     *
858
     * Magic function, supplied by php.
859
     * This function will attempt to find a variable set with `$name` and
860
     * returns it. If the variable is not set, it will return false.
861
     *
862
     * @since Symphony 2.2.2
863
     * @param string $name
864
     *  The property name.
865
     * @return boolean|mixed
866
     */
867
    public function __get($name)
868
    {
869
        return isset($this->{'_'.$name}) ? $this->{'_'.$name} : false;
870
    }
871
872
    /**
873
     * The preferences to add to the preferences pane in the admin area.
874
     *
875
     * @return XMLElement
876
     */
877
    public function getPreferencesPane()
878
    {
879
        return new XMLElement('fieldset');
880
    }
881
882
    /**
883
     * Internal function to turn underscored variables into camelcase, for
884
     * use in methods.
885
     * Because Symphony has a difference in naming between properties and
886
     * methods (underscored vs camelcased) and the Email class uses the
887
     * magic __set function to find property-setting-methods, this
888
     * conversion is needed.
889
     *
890
     * @param string $string
891
     *  The string to convert
892
     * @param boolean $caseFirst
893
     *  If this is true, the first character will be uppercased. Useful
894
     *  for method names (setName).
895
     *  If set to false, the first character will be lowercased. This is
896
     *  default behaviour.
897
     * @return string
898
     */
899
    private function __toCamel($string, $caseFirst = false)
900
    {
901
        $string = strtolower($string);
902
        $a = explode('_', $string);
903
        $a = array_map('ucfirst', $a);
904
905
        if (!$caseFirst) {
906
            $a[0] = lcfirst($a[0]);
907
        }
908
909
        return implode('', $a);
910
    }
911
912
    /**
913
     * The reverse of the __toCamel function.
914
     *
915
     * @param string $string
916
     *  The string to convert
917
     * @return string
918
     */
919
    private function __fromCamel($string)
920
    {
921
        $string[0] = strtolower($string[0]);
922
923
        return preg_replace_callback('/([A-Z])/', function($c) {
924
            return "_" . strtolower($c[1]);
925
        }, $string);
926
    }
927
}
928