Completed
Push — master ( 5c98d3...0a7e4c )
by Loz
11:47
created

Email::bcc_all_emails_to()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
204
		Deprecation::notice('4.0', 'Use "emailbouncehandler" module');
205
	}
206
207
	/**
208
	 * Attach the specified file to this email message.
209
	 *
210
	 * @param string $filename Relative or full path to file you wish to attach to this email message.
211
	 * @param string|null $attachedFilename Name of the file that should appear once it's sent as a separate attachment.
212
	 * @param string|null $mimeType MIME type to use when attaching file. If not provided, will attempt to infer via HTTP::get_mime_type().
213
	 * @return $this
214
	 */
215
	public function attachFile($filename, $attachedFilename = null, $mimeType = null) {
216
		if(!$attachedFilename) $attachedFilename = basename($filename);
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...
217
		$absoluteFileName = Director::getAbsFile($filename);
218
		if(file_exists($absoluteFileName)) {
219
			$this->attachFileFromString(file_get_contents($absoluteFileName), $attachedFilename, $mimeType);
220
		} else {
221
			user_error("Could not attach '$absoluteFileName' to email. File does not exist.", E_USER_NOTICE);
222
		}
223
		return $this;
224
	}
225
226
	/**
227
	 * @return string|null
228
	 */
229
	public function Subject() {
230
		return $this->subject;
231
	}
232
233
	/**
234
	 * @return string|null
235
	 */
236
	public function Body() {
237
		return $this->body;
238
	}
239
240
	/**
241
	 * @return string|null
242
	 */
243
	public function To() {
244
		return $this->to;
245
	}
246
247
	/**
248
	 * @return string|null
249
	 */
250
	public function From() {
251
		return $this->from;
252
	}
253
254
	/**
255
	 * @return string|null
256
	 */
257
	public function Cc() {
258
		return $this->cc;
259
	}
260
261
	/**
262
	 * @return string|null
263
	 */
264
	public function Bcc() {
265
		return $this->bcc;
266
	}
267
268
	/**
269
	 * @param string $val
270
	 * @return $this
271
	 */
272
	public function setSubject($val) {
273
		$this->subject = $val;
274
		return $this;
275
	}
276
277
	/**
278
	 * @param string $val
279
	 * @return $this
280
	 */
281
	public function setBody($val) {
282
		$this->body = $val;
283
		return $this;
284
	}
285
286
	/**
287
	 * @param string $val
288
	 * @return $this
289
	 */
290
	public function setTo($val) {
291
		$this->to = $val;
292
		return $this;
293
	}
294
295
	/**
296
	 * @param string $val
297
	 * @return $this
298
	 */
299
	public function setFrom($val) {
300
		$this->from = $val;
301
		return $this;
302
	}
303
304
	/**
305
	 * @param string $val
306
	 * @return $this
307
	 */
308
	public function setCc($val) {
309
		$this->cc = $val;
310
		return $this;
311
	}
312
313
	/**
314
	 * @param string $val
315
	 * @return $this
316
	 */
317
	public function setBcc($val) {
318
		$this->bcc = $val;
319
		return $this;
320
	}
321
322
	/**
323
	 * Set the "Reply-To" header with an email address.
324
	 *
325
	 * @param string $val
326
	 * @return $this
327
	 */
328
	public function setReplyTo($val) {
329
		$this->addCustomHeader('Reply-To', $val);
330
		return $this;
331
	}
332
333
	/**
334
	 * @param string $email
335
	 * @return $this
336
	 * @deprecated 4.0 Use the "setReplyTo" method instead
337
	 */
338
	public function replyTo($email) {
339
		Deprecation::notice('4.0', 'Use the "setReplyTo" method instead');
340
		$this->setReplyTo($email);
341
		return $this;
342
	}
343
344
	/**
345
	 * Add a custom header to this email message. Useful for implementing all those cool features that we didn't think of.
346
	 *
347
	 * IMPORTANT: If the specified header already exists, the provided value will be appended!
348
	 *
349
	 * @todo Should there be an option to replace instead of append? Or maybe a new method ->setCustomHeader()?
350
	 *
351
	 * @param string $headerName
352
	 * @param string $headerValue
353
	 * @return $this
354
	 */
355
	public function addCustomHeader($headerName, $headerValue) {
356
		if ($headerName == 'Cc') {
357
			$this->cc = $headerValue;
358
		} elseif($headerName == 'Bcc') {
359
			$this->bcc = $headerValue;
360
		} else {
361
			// Append value instead of replacing.
362
			if(isset($this->customHeaders[$headerName])) {
363
				$this->customHeaders[$headerName] .= ", " . $headerValue;
364
			} else {
365
				$this->customHeaders[$headerName] = $headerValue;
366
			}
367
		}
368
		return $this;
369
	}
370
371
	/**
372
	 * @return string
373
	 */
374
	public function BaseURL() {
375
		return Director::absoluteBaseURL();
376
	}
377
378
	/**
379
	 * Get an HTML string for debugging purposes.
380
	 *
381
	 * @return string
382
	 */
383
	public function debug() {
384
		$this->parseVariables();
385
386
		return "<h2>Email template $this->class</h2>\n" .
387
			"<p><b>From:</b> $this->from\n" .
388
			"<b>To:</b> $this->to\n" .
389
			"<b>Cc:</b> $this->cc\n" .
390
			"<b>Bcc:</b> $this->bcc\n" .
391
			"<b>Subject:</b> $this->subject</p>" .
392
			$this->body;
393
	}
394
395
	/**
396
	 * Set template name (without *.ss extension).
397
	 *
398
	 * @param string $template
399
	 * @return $this
400
	 */
401
	public function setTemplate($template) {
402
		$this->ss_template = $template;
403
		return $this;
404
	}
405
406
	/**
407
	 * @return string
408
	 */
409
	public function getTemplate() {
410
		return $this->ss_template;
411
	}
412
413
	/**
414
	 * @return $this
415
	 */
416
	protected function templateData() {
417
		if($this->template_data) {
418
			return $this->template_data->customise(array(
419
				"To" => $this->to,
420
				"Cc" => $this->cc,
421
				"Bcc" => $this->bcc,
422
				"From" => $this->from,
423
				"Subject" => $this->subject,
424
				"Body" => $this->body,
425
				"BaseURL" => $this->BaseURL(),
426
				"IsEmail" => true,
427
			));
428
		} else {
429
			return $this;
430
		}
431
	}
432
433
	/**
434
	 * Used by {@link SSViewer} templates to detect if we're rendering an email template rather than a page template
435
	 */
436
	public function IsEmail() {
437
		return true;
438
	}
439
440
	/**
441
	 * Populate this email template with values. This may be called many times.
442
	 *
443
	 * @param array|ViewableData $data
444
	 * @return $this
445
	 */
446
	public function populateTemplate($data) {
447
		if($this->template_data) {
448
			$this->template_data = $this->template_data->customise($data);
449
		} else {
450
			if(is_array($data)) $data = new ArrayData($data);
451
			$this->template_data = $this->customise($data);
452
		}
453
		$this->parseVariables_done = false;
454
455
		return $this;
456
	}
457
458
	/**
459
	 * Load all the template variables into the internal variables, including
460
	 * the template into body.	Called before send() or debugSend()
461
	 * $isPlain=true will cause the template to be ignored, otherwise the GenericEmail template will be used
462
	 * and it won't be plain email :)
463
	 *
464
	 * @param bool $isPlain
465
	 * @return $this
466
	 */
467
	protected function parseVariables($isPlain = false) {
468
		$origState = Config::inst()->get('SSViewer', 'source_file_comments');
469
		Config::inst()->update('SSViewer', 'source_file_comments', false);
470
471
		if(!$this->parseVariables_done) {
472
			$this->parseVariables_done = true;
473
474
			// Parse $ variables in the base parameters
475
			$data = $this->templateData();
476
477
			// Process a .SS template file
478
			$fullBody = $this->body;
479
			if($this->ss_template && !$isPlain) {
480
				// Requery data so that updated versions of To, From, Subject, etc are included
481
				$data = $this->templateData();
482
483
				$template = new SSViewer('email/'.$this->ss_template);
484
485
				if($template->exists()) {
486
					$fullBody = $template->process($data);
487
				}
488
			}
489
490
			// Rewrite relative URLs
491
			$this->body = HTTP::absoluteURLs($fullBody);
0 ignored issues
show
Bug introduced by
It seems like $fullBody defined by $template->process($data) on line 486 can also be of type object<SilverStripe\ORM\FieldType\DBHTMLText>; however, 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...
492
		}
493
		Config::inst()->update('SSViewer', 'source_file_comments', $origState);
494
495
		return $this;
496
	}
497
498
	/**
499
	 * @param string $address
500
	 * @return bool
501
	 * @deprecated 4.0 Use the "is_valid_address" method instead
502
	 */
503
	public static function validEmailAddress($address) {
504
		Deprecation::notice('4.0', 'Use the "is_valid_address" method instead');
505
		return static::is_valid_address($address);
506
	}
507
508
	/**
509
	 * Send the email in plaintext.
510
	 *
511
	 * @see send() for sending emails with HTML content.
512
	 * @uses Mailer->sendPlain()
513
	 *
514
	 * @param string $messageID Optional message ID so the message can be identified in bounces etc.
515
	 * @return mixed Success of the sending operation from an MTA perspective. Doesn't actually give any indication if
516
	 * the mail has been delivered to the recipient properly). See Mailer->sendPlain() for return type details.
517
	 */
518
	public function sendPlain($messageID = null) {
519
		Requirements::clear();
520
521
		$this->parseVariables(true);
522
523
		if(empty($this->from)) $this->from = Email::config()->admin_email;
0 ignored issues
show
Documentation introduced by
The property admin_email does not exist on object<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...
524
525
		$headers = $this->customHeaders;
526
527
		if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $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...
528
529
		if(project()) $headers['X-SilverStripeSite'] = project();
530
531
		$to = $this->to;
532
		$from = $this->from;
533
		$subject = $this->subject;
534
		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<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...
535
			$subject .= " [addressed to $to";
536
			$to = $sendAllTo;
537
			if($this->cc) $subject .= ", cc to $this->cc";
538
			if($this->bcc) $subject .= ", bcc to $this->bcc";
539
			$subject .= ']';
540
			unset($headers['Cc']);
541
			unset($headers['Bcc']);
542
		} else {
543
			if($this->cc) $headers['Cc'] = $this->cc;
544
			if($this->bcc) $headers['Bcc'] = $this->bcc;
545
		}
546
547
		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<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...
548
			if(!empty($headers['Cc']) && trim($headers['Cc'])) {
549
				$headers['Cc'] .= ', ' . $ccAllTo;
550
			} else {
551
				$headers['Cc'] = $ccAllTo;
552
			}
553
		}
554
555
		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<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...
556
			if(!empty($headers['Bcc']) && trim($headers['Bcc'])) {
557
				$headers['Bcc'] .= ', ' . $bccAllTo;
558
			} else {
559
				$headers['Bcc'] = $bccAllTo;
560
			}
561
		}
562
563
		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<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...
564
			if($from) $subject .= " [from $from]";
565
			$from = $sendAllfrom;
566
		}
567
568
		Requirements::restore();
569
570
		return self::mailer()->sendPlain($to, $from, $subject, $this->body, $this->attachments, $headers);
571
	}
572
573
	/**
574
	 * Send an email with HTML content.
575
	 *
576
	 * @see sendPlain() for sending plaintext emails only.
577
	 * @uses Mailer->sendHTML()
578
	 *
579
	 * @param string $messageID Optional message ID so the message can be identified in bounces etc.
580
	 * @return mixed Success of the sending operation from an MTA perspective. Doesn't actually give any indication if
581
	 * the mail has been delivered to the recipient properly). See Mailer->sendPlain() for return type details.
582
	 */
583
	public function send($messageID = null) {
584
		Requirements::clear();
585
586
		$this->parseVariables();
587
588
		if(empty($this->from)) $this->from = Email::config()->admin_email;
0 ignored issues
show
Documentation introduced by
The property admin_email does not exist on object<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...
589
590
		$headers = $this->customHeaders;
591
592
		if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $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...
593
594
		if(project()) $headers['X-SilverStripeSite'] = project();
595
596
597
		$to = $this->to;
598
		$from = $this->from;
599
		$subject = $this->subject;
600
		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<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...
601
			$subject .= " [addressed to $to";
602
			$to = $sendAllTo;
603
			if($this->cc) $subject .= ", cc to $this->cc";
604
			if($this->bcc) $subject .= ", bcc to $this->bcc";
605
			$subject .= ']';
606
			unset($headers['Cc']);
607
			unset($headers['Bcc']);
608
609
		} else {
610
			if($this->cc) $headers['Cc'] = $this->cc;
611
			if($this->bcc) $headers['Bcc'] = $this->bcc;
612
		}
613
614
615
		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<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...
616
			if(!empty($headers['Cc']) && trim($headers['Cc'])) {
617
				$headers['Cc'] .= ', ' . $ccAllTo;
618
			} else {
619
				$headers['Cc'] = $ccAllTo;
620
			}
621
		}
622
623
		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<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...
624
			if(!empty($headers['Bcc']) && trim($headers['Bcc'])) {
625
				$headers['Bcc'] .= ', ' . $bccAllTo;
626
			} else {
627
				$headers['Bcc'] = $bccAllTo;
628
			}
629
		}
630
631
		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<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...
632
			if($from) $subject .= " [from $from]";
633
			$from = $sendAllfrom;
634
		}
635
636
		Requirements::restore();
637
638
		return self::mailer()->sendHTML($to, $from, $subject, $this->body, $this->attachments, $headers,
639
			$this->plaintext_body);
640
	}
641
642
	/**
643
	 * Used as a default sender address in the {@link Email} class
644
	 * unless overwritten. Also shown to users on live environments
645
	 * as a contact address on system error pages.
646
	 *
647
	 * Used by {@link Email->send()}, {@link Email->sendPlain()}, {@link Debug->friendlyError()}.
648
	 *
649
	 * @deprecated 4.0 Use the "Email.admin_email" config setting instead
650
	 * @param string $newEmail
651
	 */
652
	public static function setAdminEmail($newEmail) {
653
		Deprecation::notice('4.0', 'Use the "Email.admin_email" config setting instead');
654
		Config::inst()->update('Email', 'admin_email', $newEmail);
655
	}
656
657
	/**
658
	 * @deprecated 4.0 Use the "Email.admin_email" config setting instead
659
	 * @return string
660
	 */
661
	public static function getAdminEmail() {
662
		Deprecation::notice('4.0', 'Use the "Email.admin_email" config setting instead');
663
		return Config::inst()->get('Email', 'admin_email');
664
	}
665
666
	/**
667
	 * Send every email generated by the Email class to the given address.
668
	 * It will also add " [addressed to (email), cc to (email), bcc to (email)]" to the end of the subject line
669
	 * This can be used when testing, by putting a command like this in your _config.php file
670
	 *
671
	 * if(!Director::isLive()) Email::send_all_emails_to("[email protected]")
672
	 *
673
	 * @deprecated 4.0 Use the "Email.send_all_emails_to" config setting instead
674
	 */
675
	public static function send_all_emails_to($emailAddress) {
676
		Deprecation::notice('4.0', 'Use the "Email.send_all_emails_to" config setting instead');
677
		Config::inst()->update('Email', 'send_all_emails_to', $emailAddress);
678
	}
679
680
	/**
681
	 * CC every email generated by the Email class to the given address.
682
	 * It won't affect the original delivery in the same way that send_all_emails_to does.	It just adds a CC header
683
	 * with the given email address.	Note that you can only call this once - subsequent calls will overwrite the
684
	 * configuration variable.
685
	 *
686
	 * This can be used when you have a system that relies heavily on email and you want someone to be checking all
687
	 * correspondence.
688
	 *
689
	 * if(Director::isLive()) Email::cc_all_emails_to("[email protected]")
690
	 *
691
	 * @deprecated 4.0 Use the "Email.cc_all_emails_to" config setting instead
692
	 */
693
	public static function cc_all_emails_to($emailAddress) {
694
		Deprecation::notice('4.0', 'Use the "Email.cc_all_emails_to" config setting instead');
695
		Config::inst()->update('Email', 'cc_all_emails_to', $emailAddress);
696
	}
697
698
	/**
699
	 * BCC every email generated by the Email class to the given address.
700
	 * It won't affect the original delivery in the same way that send_all_emails_to does.	It just adds a BCC header
701
	 * with the given email address.	Note that you can only call this once - subsequent calls will overwrite the
702
	 * configuration variable.
703
	 *
704
	 * This can be used when you have a system that relies heavily on email and you want someone to be checking all
705
	 * correspondence.
706
	 *
707
	 * if(Director::isLive()) Email::cc_all_emails_to("[email protected]")
708
	 *
709
	 * @deprecated 4.0 Use the "Email.bcc_all_emails_to" config setting instead
710
	 */
711
	public static function bcc_all_emails_to($emailAddress) {
712
		Deprecation::notice('4.0', 'Use the "Email.bcc_all_emails_to" config setting instead');
713
		Config::inst()->update('Email', 'bcc_all_emails_to', $emailAddress);
714
	}
715
716
	/**
717
	 * Validates the email address to get as close to RFC 822 compliant as possible.
718
	 *
719
	 * @param string $email
720
	 * @return bool
721
	 *
722
	 * @copyright Cal Henderson <[email protected]>
723
	 * 	This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
724
	 * 	http://creativecommons.org/licenses/by-sa/2.5/
725
	 */
726
	public static function is_valid_address($email){
727
		$qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
728
		$dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
729
		$atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c'.
730
			'\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
731
		$quoted_pair = '\\x5c[\\x00-\\x7f]';
732
		$domain_literal = "\\x5b($dtext|$quoted_pair)*\\x5d";
733
		$quoted_string = "\\x22($qtext|$quoted_pair)*\\x22";
734
		$domain_ref = $atom;
735
		$sub_domain = "($domain_ref|$domain_literal)";
736
		$word = "($atom|$quoted_string)";
737
		$domain = "$sub_domain(\\x2e$sub_domain)*";
738
		$local_part = "$word(\\x2e$word)*";
739
		$addr_spec = "$local_part\\x40$domain";
740
741
		return preg_match("!^$addr_spec$!", $email) === 1;
742
	}
743
744
	/**
745
	 * Encode an email-address to help protect it from spam bots. At the moment only simple string substitutions, which
746
	 * are not 100% safe from email harvesting.
747
	 *
748
	 * @todo Integrate javascript-based solution
749
	 *
750
	 * @param string $email Email-address
751
	 * @param string $method Method for obfuscating/encoding the address
752
	 *	- 'direction': Reverse the text and then use CSS to put the text direction back to normal
753
	 *	- 'visible': Simple string substitution ('@' to '[at]', '.' to '[dot], '-' to [dash])
754
	 *	- 'hex': Hexadecimal URL-Encoding - useful for mailto: links
755
	 * @return string
756
	 */
757
	public static function obfuscate($email, $method = 'visible') {
758
		switch($method) {
759
			case 'direction' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
760
				Requirements::customCSS(
761
					'span.codedirection { unicode-bidi: bidi-override; direction: rtl; }',
762
					'codedirectionCSS'
763
				);
764
				return '<span class="codedirection">' . strrev($email) . '</span>';
765
			case 'visible' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
766
				$obfuscated = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
767
				return strtr($email, $obfuscated);
768
			case 'hex' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
769
				$encoded = '';
770
				for ($x=0; $x < strlen($email); $x++) $encoded .= '&#x' . bin2hex($email{$x}).';';
771
				return $encoded;
772
			default:
773
				user_error('Email::obfuscate(): Unknown obfuscation method', E_USER_NOTICE);
774
				return $email;
775
		}
776
	}
777
}
778