Completed
Push — master ( 1be2e7...d38097 )
by Sam
23s
created

Email::obfuscate()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 24
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 19
nc 5
nop 2
dl 0
loc 24
rs 8.5125
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control\Email;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Control\HTTP;
7
use SilverStripe\Core\Injector\Injector;
8
use SilverStripe\Dev\Deprecation;
9
use SilverStripe\View\ArrayData;
10
use SilverStripe\View\SSViewer;
11
use SilverStripe\View\Requirements;
12
use SilverStripe\View\ViewableData;
13
use SilverStripe\View\ViewableData_Customised;
14
15
if (isset($_SERVER['SERVER_NAME'])) {
16
    /**
17
     * X-Mailer header value on emails sent
18
     */
19
    define('X_MAILER', 'SilverStripe Mailer - version 2006.06.21 (Sent from "'.$_SERVER['SERVER_NAME'].'")');
20
} else {
21
    /**
22
     * @ignore
23
     */
24
    define('X_MAILER', 'SilverStripe Mailer - version 2006.06.21');
25
}
26
27
/**
28
 * Class to support sending emails.
29
 */
30
class Email extends ViewableData
31
{
32
33
    /**
34
     * @var string $from Email-Address
35
     */
36
    protected $from;
37
38
    /**
39
     * @var string $to Email-Address. Use comma-separation to pass multiple email-addresses.
40
     */
41
    protected $to;
42
43
    /**
44
     * @var string $subject Subject of the email
45
     */
46
    protected $subject;
47
48
    /**
49
     * Passed straight into {@link $ss_template} as $Body variable.
50
     *
51
     * @var string $body HTML content of the email.
52
     */
53
    protected $body;
54
55
    /**
56
     * If not set, defaults to converting the HTML-body with {@link Convert::xml2raw()}.
57
     *
58
     * @var string $plaintext_body Optional string for plaintext emails.
59
     */
60
    protected $plaintext_body;
61
62
    /**
63
     * @var string $cc
64
     */
65
    protected $cc;
66
67
    /**
68
     * @var string $bcc
69
     */
70
    protected $bcc;
71
72
    /**
73
     * @var array $customHeaders A map of header-name -> header-value
74
     */
75
    protected $customHeaders = array();
76
77
    /**
78
     * @var array $attachments Internal, use {@link attachFileFromString()} or {@link attachFile()}
79
     */
80
    protected $attachments = array();
81
82
    /**
83
     * @var boolean $parseVariables_done
84
     */
85
    protected $parseVariables_done = false;
86
87
    /**
88
     * @var string $ss_template The name of the used template (without *.ss extension)
89
     */
90
    protected $ss_template = 'GenericEmail';
91
92
    /**
93
     * Used in the same way than {@link ViewableData->customize()}.
94
     *
95
     * @var ViewableData_Customised $template_data Additional data available in a template.
96
     */
97
    protected $template_data;
98
99
    /**
100
     * This will be set in the config on a site-by-site basis
101
     *
102
     * @config
103
     * @var string The default administrator email address.
104
     */
105
    private static $admin_email = '';
106
107
    /**
108
     * Send every email generated by the Email class to the given address.
109
     *
110
     * It will also add " [addressed to (email), cc to (email), bcc to (email)]" to the end of the subject line
111
     *
112
     * To set this, set Email.send_all_emails_to in your yml config file.
113
     * It can also be set in _ss_environment.php with SS_SEND_ALL_EMAILS_TO.
114
     *
115
     * @config
116
     * @var string $send_all_emails_to Email-Address
117
     */
118
    private static $send_all_emails_to;
119
120
    /**
121
     * Send every email generated by the Email class *from* the given address.
122
     * It will also add " [, from to (email)]" to the end of the subject line
123
     *
124
     * To set this, set Email.send_all_emails_from in your yml config file.
125
     * It can also be set in _ss_environment.php with SS_SEND_ALL_EMAILS_FROM.
126
     *
127
     * @config
128
     * @var string $send_all_emails_from Email-Address
129
     */
130
    private static $send_all_emails_from;
131
132
    /**
133
     * @config
134
     * @var string BCC every email generated by the Email class to the given address.
135
     */
136
    private static $bcc_all_emails_to;
137
138
    /**
139
     * @config
140
     * @var string CC every email generated by the Email class to the given address.
141
     */
142
    private static $cc_all_emails_to;
143
144
    /**
145
     * Create a new email.
146
     *
147
     * @param string|null $from
148
     * @param string|null $to
149
     * @param string|null $subject
150
     * @param string|null $body
151
     * @param string|null $bounceHandlerURL
152
     * @param string|null $cc
153
     * @param string|null $bcc
154
     */
155
    public function __construct(
156
        $from = null,
157
        $to = null,
158
        $subject = null,
159
        $body = null,
160
        $bounceHandlerURL = null,
161
        $cc = null,
162
        $bcc = null
163
    ) {
164
165
        if ($from !== null) {
166
            $this->from = $from;
167
        }
168
        if ($to !== null) {
169
            $this->to = $to;
170
        }
171
        if ($subject !== null) {
172
            $this->subject = $subject;
173
        }
174
        if ($body !== null) {
175
            $this->body = $body;
176
        }
177
        if ($cc !== null) {
178
            $this->cc = $cc;
179
        }
180
        if ($bcc !== null) {
181
            $this->bcc = $bcc;
182
        }
183
184
        if ($bounceHandlerURL !== null) {
185
            Deprecation::notice('4.0', 'Use "emailbouncehandler" module');
186
        }
187
188
        parent::__construct();
189
    }
190
191
    /**
192
     * Get the mailer.
193
     *
194
     * @return Mailer
195
     */
196
    public static function mailer()
197
    {
198
        return Injector::inst()->get('SilverStripe\\Control\\Email\\Mailer');
199
    }
200
201
    /**
202
     * Attach a file based on provided raw data.
203
     *
204
     * @param string $data The raw file data (not encoded).
205
     * @param string $attachedFilename Name of the file that should appear once it's sent as a separate attachment.
206
     * @param string|null $mimeType MIME type to use when attaching file. If not provided, will attempt to infer via HTTP::get_mime_type().
207
     * @return $this
208
     */
209
    public function attachFileFromString($data, $attachedFilename, $mimeType = null)
210
    {
211
        $this->attachments[] = array(
212
            'contents' => $data,
213
            'filename' => $attachedFilename,
214
            'mimetype' => $mimeType,
215
        );
216
        return $this;
217
    }
218
219
    /**
220
     * Attach the specified file to this email message.
221
     *
222
     * @param string $filename Relative or full path to file you wish to attach to this email message.
223
     * @param string|null $attachedFilename Name of the file that should appear once it's sent as a separate attachment.
224
     * @param string|null $mimeType MIME type to use when attaching file. If not provided, will attempt to infer via HTTP::get_mime_type().
225
     * @return $this
226
     */
227
    public function attachFile($filename, $attachedFilename = null, $mimeType = null)
228
    {
229
        if (!$attachedFilename) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attachedFilename of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
230
            $attachedFilename = basename($filename);
231
        }
232
        $absoluteFileName = Director::getAbsFile($filename);
233
        if (file_exists($absoluteFileName)) {
234
            $this->attachFileFromString(file_get_contents($absoluteFileName), $attachedFilename, $mimeType);
235
        } else {
236
            user_error("Could not attach '$absoluteFileName' to email. File does not exist.", E_USER_NOTICE);
237
        }
238
        return $this;
239
    }
240
241
    /**
242
     * @return string|null
243
     */
244
    public function Subject()
245
    {
246
        return $this->subject;
247
    }
248
249
    /**
250
     * @return string|null
251
     */
252
    public function Body()
253
    {
254
        return $this->body;
255
    }
256
257
    /**
258
     * @return string|null
259
     */
260
    public function To()
261
    {
262
        return $this->to;
263
    }
264
265
    /**
266
     * @return string|null
267
     */
268
    public function From()
269
    {
270
        return $this->from;
271
    }
272
273
    /**
274
     * @return string|null
275
     */
276
    public function Cc()
277
    {
278
        return $this->cc;
279
    }
280
281
    /**
282
     * @return string|null
283
     */
284
    public function Bcc()
285
    {
286
        return $this->bcc;
287
    }
288
289
    /**
290
     * @param string $val
291
     * @return $this
292
     */
293
    public function setSubject($val)
294
    {
295
        $this->subject = $val;
296
        return $this;
297
    }
298
299
    /**
300
     * @param string $val
301
     * @return $this
302
     */
303
    public function setBody($val)
304
    {
305
        $this->body = $val;
306
        return $this;
307
    }
308
309
    /**
310
     * @param string $val
311
     * @return $this
312
     */
313
    public function setTo($val)
314
    {
315
        $this->to = $val;
316
        return $this;
317
    }
318
319
    /**
320
     * @param string $val
321
     * @return $this
322
     */
323
    public function setFrom($val)
324
    {
325
        $this->from = $val;
326
        return $this;
327
    }
328
329
    /**
330
     * @param string $val
331
     * @return $this
332
     */
333
    public function setCc($val)
334
    {
335
        $this->cc = $val;
336
        return $this;
337
    }
338
339
    /**
340
     * @param string $val
341
     * @return $this
342
     */
343
    public function setBcc($val)
344
    {
345
        $this->bcc = $val;
346
        return $this;
347
    }
348
349
    /**
350
     * Set the "Reply-To" header with an email address.
351
     *
352
     * @param string $val
353
     * @return $this
354
     */
355
    public function setReplyTo($val)
356
    {
357
        $this->addCustomHeader('Reply-To', $val);
358
        return $this;
359
    }
360
361
    /**
362
     * Add a custom header to this email message. Useful for implementing all those cool features that we didn't think of.
363
     *
364
     * IMPORTANT: If the specified header already exists, the provided value will be appended!
365
     *
366
     * @todo Should there be an option to replace instead of append? Or maybe a new method ->setCustomHeader()?
367
     *
368
     * @param string $headerName
369
     * @param string $headerValue
370
     * @return $this
371
     */
372
    public function addCustomHeader($headerName, $headerValue)
373
    {
374
        if ($headerName == 'Cc') {
375
            $this->cc = $headerValue;
376
        } elseif ($headerName == 'Bcc') {
377
            $this->bcc = $headerValue;
378
        } else {
379
            // Append value instead of replacing.
380
            if (isset($this->customHeaders[$headerName])) {
381
                $this->customHeaders[$headerName] .= ", " . $headerValue;
382
            } else {
383
                $this->customHeaders[$headerName] = $headerValue;
384
            }
385
        }
386
        return $this;
387
    }
388
389
    /**
390
     * @return string
391
     */
392
    public function BaseURL()
393
    {
394
        return Director::absoluteBaseURL();
395
    }
396
397
    /**
398
     * Get an HTML string for debugging purposes.
399
     *
400
     * @return string
401
     */
402
    public function debug()
403
    {
404
        $this->parseVariables();
405
406
        return "<h2>Email template $this->class</h2>\n" .
407
            "<p><b>From:</b> $this->from\n" .
408
            "<b>To:</b> $this->to\n" .
409
            "<b>Cc:</b> $this->cc\n" .
410
            "<b>Bcc:</b> $this->bcc\n" .
411
            "<b>Subject:</b> $this->subject</p>" .
412
            $this->body;
413
    }
414
415
    /**
416
     * Set template name (without *.ss extension).
417
     *
418
     * @param string $template
419
     * @return $this
420
     */
421
    public function setTemplate($template)
422
    {
423
        $this->ss_template = $template;
424
        return $this;
425
    }
426
427
    /**
428
     * @return string
429
     */
430
    public function getTemplate()
431
    {
432
        return $this->ss_template;
433
    }
434
435
    /**
436
     * @return Email|ViewableData_Customised
437
     */
438
    protected function templateData()
439
    {
440
        if ($this->template_data) {
441
            return $this->template_data->customise(array(
442
                "To" => $this->to,
443
                "Cc" => $this->cc,
444
                "Bcc" => $this->bcc,
445
                "From" => $this->from,
446
                "Subject" => $this->subject,
447
                "Body" => $this->body,
448
                "BaseURL" => $this->BaseURL(),
449
                "IsEmail" => true,
450
            ));
451
        } else {
452
            return $this;
453
        }
454
    }
455
456
    /**
457
     * Used by {@link SSViewer} templates to detect if we're rendering an email template rather than a page template
458
     */
459
    public function IsEmail()
460
    {
461
        return true;
462
    }
463
464
    /**
465
     * Populate this email template with values. This may be called many times.
466
     *
467
     * @param array|ViewableData $data
468
     * @return $this
469
     */
470
    public function populateTemplate($data)
471
    {
472
        if ($this->template_data) {
473
            $this->template_data = $this->template_data->customise($data);
474
        } else {
475
            if (is_array($data)) {
476
                $data = new ArrayData($data);
477
            }
478
            $this->template_data = $this->customise($data);
479
        }
480
        $this->parseVariables_done = false;
481
482
        return $this;
483
    }
484
485
    /**
486
     * Load all the template variables into the internal variables, including
487
     * the template into body.  Called before send() or debugSend()
488
     * $isPlain=true will cause the template to be ignored, otherwise the GenericEmail template will be used
489
     * and it won't be plain email :)
490
     *
491
     * @param bool $isPlain
492
     * @return $this
493
     */
494
    protected function parseVariables($isPlain = false)
495
    {
496
        $origState = SSViewer::config()->get('source_file_comments');
497
        SSViewer::config()->update('source_file_comments', false);
498
499
        if (!$this->parseVariables_done) {
500
            $this->parseVariables_done = true;
501
502
            // Parse $ variables in the base parameters
503
            $this->templateData();
504
505
            // Process a .SS template file
506
            $fullBody = $this->body;
507
            if ($this->ss_template && !$isPlain) {
508
                // Requery data so that updated versions of To, From, Subject, etc are included
509
                $data = $this->templateData();
510
                $candidateTemplates = [
511
                    $this->ss_template,
512
                    [ 'type' => 'email', $this->ss_template ]
513
                ];
514
                $template = new SSViewer($candidateTemplates);
515
                if ($template->exists()) {
516
                    $fullBody = $template->process($data);
517
                }
518
            }
519
520
            // Rewrite relative URLs
521
            $this->body = HTTP::absoluteURLs($fullBody);
0 ignored issues
show
Bug introduced by
It seems like $fullBody defined by $template->process($data) on line 516 can also be of type object<SilverStripe\ORM\FieldType\DBHTMLText>; however, SilverStripe\Control\HTTP::absoluteURLs() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
522
        }
523
        SSViewer::config()->update('source_file_comments', $origState);
524
525
        return $this;
526
    }
527
528
    /**
529
     * Send the email in plaintext.
530
     *
531
     * @see send() for sending emails with HTML content.
532
     * @uses Mailer->sendPlain()
533
     *
534
     * @param string $messageID Optional message ID so the message can be identified in bounces etc.
535
     * @return mixed Success of the sending operation from an MTA perspective. Doesn't actually give any indication if
536
     * the mail has been delivered to the recipient properly). See Mailer->sendPlain() for return type details.
537
     */
538
    public function sendPlain($messageID = null)
539
    {
540
        Requirements::clear();
541
542
        $this->parseVariables(true);
543
544
        if (empty($this->from)) {
545
            $this->from = Email::config()->admin_email;
0 ignored issues
show
Documentation introduced by
The property admin_email does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
546
        }
547
548
        $headers = $this->customHeaders;
549
550
        if ($messageID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $messageID of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
551
            $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID;
552
        }
553
554
        if (project()) {
555
            $headers['X-SilverStripeSite'] = project();
556
        }
557
558
        $to = $this->to;
559
        $from = $this->from;
560
        $subject = $this->subject;
561
        if ($sendAllTo = $this->config()->send_all_emails_to) {
0 ignored issues
show
Documentation introduced by
The property send_all_emails_to does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
562
            $subject .= " [addressed to $to";
563
            $to = $sendAllTo;
564
            if ($this->cc) {
565
                $subject .= ", cc to $this->cc";
566
            }
567
            if ($this->bcc) {
568
                $subject .= ", bcc to $this->bcc";
569
            }
570
            $subject .= ']';
571
            unset($headers['Cc']);
572
            unset($headers['Bcc']);
573
        } else {
574
            if ($this->cc) {
575
                $headers['Cc'] = $this->cc;
576
            }
577
            if ($this->bcc) {
578
                $headers['Bcc'] = $this->bcc;
579
            }
580
        }
581
582
        if ($ccAllTo = $this->config()->cc_all_emails_to) {
0 ignored issues
show
Documentation introduced by
The property cc_all_emails_to does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
583
            if (!empty($headers['Cc']) && trim($headers['Cc'])) {
584
                $headers['Cc'] .= ', ' . $ccAllTo;
585
            } else {
586
                $headers['Cc'] = $ccAllTo;
587
            }
588
        }
589
590
        if ($bccAllTo = $this->config()->bcc_all_emails_to) {
0 ignored issues
show
Documentation introduced by
The property bcc_all_emails_to does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
591
            if (!empty($headers['Bcc']) && trim($headers['Bcc'])) {
592
                $headers['Bcc'] .= ', ' . $bccAllTo;
593
            } else {
594
                $headers['Bcc'] = $bccAllTo;
595
            }
596
        }
597
598
        if ($sendAllfrom = $this->config()->send_all_emails_from) {
0 ignored issues
show
Documentation introduced by
The property send_all_emails_from does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
599
            if ($from) {
600
                $subject .= " [from $from]";
601
            }
602
            $from = $sendAllfrom;
603
        }
604
605
        Requirements::restore();
606
607
        return self::mailer()->sendPlain($to, $from, $subject, $this->body, $this->attachments, $headers);
608
    }
609
610
    /**
611
     * Send an email with HTML content.
612
     *
613
     * @see sendPlain() for sending plaintext emails only.
614
     * @uses Mailer->sendHTML()
615
     *
616
     * @param string $messageID Optional message ID so the message can be identified in bounces etc.
617
     * @return mixed Success of the sending operation from an MTA perspective. Doesn't actually give any indication if
618
     * the mail has been delivered to the recipient properly). See Mailer->sendPlain() for return type details.
619
     */
620
    public function send($messageID = null)
621
    {
622
        Requirements::clear();
623
624
        $this->parseVariables();
625
626
        if (empty($this->from)) {
627
            $this->from = Email::config()->admin_email;
0 ignored issues
show
Documentation introduced by
The property admin_email does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
628
        }
629
630
        $headers = $this->customHeaders;
631
632
        if ($messageID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $messageID of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
633
            $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID;
634
        }
635
636
        if (project()) {
637
            $headers['X-SilverStripeSite'] = project();
638
        }
639
640
641
        $to = $this->to;
642
        $from = $this->from;
643
        $subject = $this->subject;
644
        if ($sendAllTo = $this->config()->send_all_emails_to) {
0 ignored issues
show
Documentation introduced by
The property send_all_emails_to does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
645
            $subject .= " [addressed to $to";
646
            $to = $sendAllTo;
647
            if ($this->cc) {
648
                $subject .= ", cc to $this->cc";
649
            }
650
            if ($this->bcc) {
651
                $subject .= ", bcc to $this->bcc";
652
            }
653
            $subject .= ']';
654
            unset($headers['Cc']);
655
            unset($headers['Bcc']);
656
        } else {
657
            if ($this->cc) {
658
                $headers['Cc'] = $this->cc;
659
            }
660
            if ($this->bcc) {
661
                $headers['Bcc'] = $this->bcc;
662
            }
663
        }
664
665
666
        if ($ccAllTo = $this->config()->cc_all_emails_to) {
0 ignored issues
show
Documentation introduced by
The property cc_all_emails_to does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
667
            if (!empty($headers['Cc']) && trim($headers['Cc'])) {
668
                $headers['Cc'] .= ', ' . $ccAllTo;
669
            } else {
670
                $headers['Cc'] = $ccAllTo;
671
            }
672
        }
673
674
        if ($bccAllTo = $this->config()->bcc_all_emails_to) {
0 ignored issues
show
Documentation introduced by
The property bcc_all_emails_to does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
675
            if (!empty($headers['Bcc']) && trim($headers['Bcc'])) {
676
                $headers['Bcc'] .= ', ' . $bccAllTo;
677
            } else {
678
                $headers['Bcc'] = $bccAllTo;
679
            }
680
        }
681
682
        if ($sendAllfrom = $this->config()->send_all_emails_from) {
0 ignored issues
show
Documentation introduced by
The property send_all_emails_from does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
683
            if ($from) {
684
                $subject .= " [from $from]";
685
            }
686
            $from = $sendAllfrom;
687
        }
688
689
        Requirements::restore();
690
691
        return self::mailer()->sendHTML(
692
            $to,
693
            $from,
694
            $subject,
695
            $this->body,
696
            $this->attachments,
697
            $headers,
698
            $this->plaintext_body
699
        );
700
    }
701
702
    /**
703
     * Validates the email address to get as close to RFC 822 compliant as possible.
704
     *
705
     * @param string $email
706
     * @return bool
707
     *
708
     * @copyright Cal Henderson <[email protected]>
709
     *  This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
710
     *  http://creativecommons.org/licenses/by-sa/2.5/
711
     */
712
    public static function is_valid_address($email)
713
    {
714
        $qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
715
        $dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
716
        $atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c'.
717
            '\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
718
        $quoted_pair = '\\x5c[\\x00-\\x7f]';
719
        $domain_literal = "\\x5b($dtext|$quoted_pair)*\\x5d";
720
        $quoted_string = "\\x22($qtext|$quoted_pair)*\\x22";
721
        $domain_ref = $atom;
722
        $sub_domain = "($domain_ref|$domain_literal)";
723
        $word = "($atom|$quoted_string)";
724
        $domain = "$sub_domain(\\x2e$sub_domain)*";
725
        $local_part = "$word(\\x2e$word)*";
726
        $addr_spec = "$local_part\\x40$domain";
727
728
        return preg_match("!^$addr_spec$!", $email) === 1;
729
    }
730
731
    /**
732
     * Encode an email-address to help protect it from spam bots. At the moment only simple string substitutions, which
733
     * are not 100% safe from email harvesting.
734
     *
735
     * @todo Integrate javascript-based solution
736
     *
737
     * @param string $email Email-address
738
     * @param string $method Method for obfuscating/encoding the address
739
     *  - 'direction': Reverse the text and then use CSS to put the text direction back to normal
740
     *  - 'visible': Simple string substitution ('@' to '[at]', '.' to '[dot], '-' to [dash])
741
     *  - 'hex': Hexadecimal URL-Encoding - useful for mailto: links
742
     * @return string
743
     */
744
    public static function obfuscate($email, $method = 'visible')
745
    {
746
        switch ($method) {
747
            case 'direction':
748
                Requirements::customCSS(
749
                    'span.codedirection { unicode-bidi: bidi-override; direction: rtl; }',
750
                    'codedirectionCSS'
751
                );
752
                return '<span class="codedirection">' . strrev($email) . '</span>';
753
            case 'visible':
754
                $obfuscated = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
755
                return strtr($email, $obfuscated);
756
            case 'hex':
757
                $encoded = '';
758
                for ($x=0; $x < strlen($email);
759
                $x++) {
760
                    $encoded .= '&#x' . bin2hex($email{$x}).';';
761
                }
762
                return $encoded;
763
            default:
764
                user_error('Email::obfuscate(): Unknown obfuscation method', E_USER_NOTICE);
765
                return $email;
766
        }
767
    }
768
}
769