FIX for #5784: Added ->setReplyTo(), deprecated ->replyTo() for API consistency. Revamping, fixing, and enhancing internal Email API documentation. Simplified code and brought up-to-date with latest standards.

This commit is contained in:
Patrick Nelson 2016-07-08 13:03:30 -07:00
parent 4cd29e80b7
commit 74c555e004
3 changed files with 195 additions and 100 deletions

View File

@ -121,7 +121,7 @@ You can set the default sender address of emails through the `Email.admin_email`
<div class="alert" markdown="1">
Remember, setting a `from` address that doesn't come from your domain (such as the users email) will likely see your
email marked as spam. If you want to send from another address think about using the `replyTo` method.
email marked as spam. If you want to send from another address think about using the `setReplyTo` method.
</div>
## Redirecting Emails
@ -144,11 +144,13 @@ Configuration of those properties looks like the following:
Config::inst()->update('Email', 'send_all_emails_to', "developer@example.com");
}
### Setting custom replyTo
### Setting custom "Reply To" email address.
For email messages that should have an email address which is replied to that actually differs from the original "from" email, do the following. This is encouraged especially when the domain responsible for sending the message isn't necessarily the same which should be used for return correspondence and should help prevent your message from being marked as spam.
:::php
$email = new Email(..);
$email->replyTo('me@address.com');
$email->setReplyTo('me@address.com');
### Setting Custom Headers

View File

@ -25,89 +25,76 @@ if(isset($_SERVER['SERVER_NAME'])) {
class Email extends ViewableData {
/**
* @param string $from Email-Address
* @var string $from Email-Address
*/
protected $from;
/**
* @param string $to Email-Address. Use comma-separation to pass multiple email-addresses.
* @var string $to Email-Address. Use comma-separation to pass multiple email-addresses.
*/
protected $to;
/**
* @param string $subject Subject of the email
* @var string $subject Subject of the email
*/
protected $subject;
/**
* @param string $body HTML content of the email.
* Passed straight into {@link $ss_template} as $Body variable.
*
* @var string $body HTML content of the email.
*/
protected $body;
/**
* @param string $plaintext_body Optional string for plaintext emails.
* If not set, defaults to converting the HTML-body with {@link Convert::xml2raw()}.
*
* @var string $plaintext_body Optional string for plaintext emails.
*/
protected $plaintext_body;
/**
* @param string $cc
* @var string $cc
*/
protected $cc;
/**
* @param string $bcc
* @var string $bcc
*/
protected $bcc;
/**
* @deprecated since version 4.0
*/
public static function set_mailer(Mailer $mailer) {
Deprecation::notice('4.0', 'Use Injector to override the Mailer service');
Injector::inst()->registerService($mailer, 'Mailer');
}
/**
* Get the mailer.
*
* @return Mailer
*/
public static function mailer() {
return Injector::inst()->get('Mailer');
}
/**
* @param array $customHeaders A map of header-name -> header-value
* @var array $customHeaders A map of header-name -> header-value
*/
protected $customHeaders = array();
/**
* @param array $attachements Internal, use {@link attachFileFromString()} or {@link attachFile()}
* @var array $attachments Internal, use {@link attachFileFromString()} or {@link attachFile()}
*/
protected $attachments = array();
/**
* @param boolean $
* @var boolean $parseVariables_done
*/
protected $parseVariables_done = false;
/**
* @param string $ss_template The name of the used template (without *.ss extension)
* @var string $ss_template The name of the used template (without *.ss extension)
*/
protected $ss_template = "GenericEmail";
protected $ss_template = 'GenericEmail';
/**
* @param array $template_data Additional data available in a template.
* Used in the same way than {@link ViewableData->customize()}.
*
* @var ViewableData_Customised $template_data Additional data available in a template.
*/
protected $template_data = null;
protected $template_data;
/**
* This will be set in the config on a site-by-site basis
*
* @config
* @var string The default administrator email address.
* This will be set in the config on a site-by-site basis
*/
private static $admin_email = '';
@ -120,9 +107,9 @@ class Email extends ViewableData {
* It can also be set in _ss_environment.php with SS_SEND_ALL_EMAILS_TO.
*
* @config
* @param string $send_all_emails_to Email-Address
* @var string $send_all_emails_to Email-Address
*/
private static $send_all_emails_to = null;
private static $send_all_emails_to;
/**
* Send every email generated by the Email class *from* the given address.
@ -132,47 +119,80 @@ class Email extends ViewableData {
* It can also be set in _ss_environment.php with SS_SEND_ALL_EMAILS_FROM.
*
* @config
* @param string $send_all_emails_from Email-Address
* @var string $send_all_emails_from Email-Address
*/
private static $send_all_emails_from = null;
private static $send_all_emails_from;
/**
* @config
* @param string BCC every email generated by the Email class to the given address.
* @var string BCC every email generated by the Email class to the given address.
*/
private static $bcc_all_emails_to = null;
private static $bcc_all_emails_to;
/**
* @config
* @param string CC every email generated by the Email class to the given address.
* @var string CC every email generated by the Email class to the given address.
*/
private static $cc_all_emails_to = null;
private static $cc_all_emails_to;
/**
* Create a new email.
*
* @param string|null $from
* @param string|null $to
* @param string|null $subject
* @param string|null $body
* @param string|null $bounceHandlerURL
* @param string|null $cc
* @param string|null $bcc
*/
public function __construct($from = null, $to = null, $subject = null, $body = null, $bounceHandlerURL = null,
$cc = null, $bcc = null) {
if($from != null) $this->from = $from;
if($to != null) $this->to = $to;
if($subject != null) $this->subject = $subject;
if($body != null) $this->body = $body;
if($cc != null) $this->cc = $cc;
if($bcc != null) $this->bcc = $bcc;
if($from !== null) $this->from = $from;
if($to !== null) $this->to = $to;
if($subject !== null) $this->subject = $subject;
if($body !== null) $this->body = $body;
if($cc !== null) $this->cc = $cc;
if($bcc !== null) $this->bcc = $bcc;
if($bounceHandlerURL != null) {
if($bounceHandlerURL !== null) {
Deprecation::notice('4.0', 'Use "emailbouncehandler" module');
}
parent::__construct();
}
public function attachFileFromString($data, $filename, $mimetype = null) {
/**
* Get the mailer.
*
* @return Mailer
*/
public static function mailer() {
return Injector::inst()->get('Mailer');
}
/**
* @deprecated since version 4.0
*/
public static function set_mailer(Mailer $mailer) {
Deprecation::notice('4.0', 'Use Injector to override the Mailer service');
Injector::inst()->registerService($mailer, 'Mailer');
}
/**
* Attach a file based on provided raw data.
*
* @param string $data The raw file data (not encoded).
* @param string $attachedFilename Name of the file that should appear once it's sent as a separate attachment.
* @param string|null $mimeType MIME type to use when attaching file. If not provided, will attempt to infer via HTTP::get_mime_type().
* @return $this
*/
public function attachFileFromString($data, $attachedFilename, $mimeType = null) {
$this->attachments[] = array(
'contents' => $data,
'filename' => $filename,
'mimetype' => $mimetype,
'filename' => $attachedFilename,
'mimetype' => $mimeType,
);
return $this;
}
@ -184,66 +204,116 @@ class Email extends ViewableData {
Deprecation::notice('4.0', 'Use "emailbouncehandler" module');
}
public function attachFile($filename, $attachedFilename = null, $mimetype = null) {
/**
* Attach the specified file to this email message.
*
* @param string $filename Relative or full path to file you wish to attach to this email message.
* @param string|null $attachedFilename Name of the file that should appear once it's sent as a separate attachment.
* @param string|null $mimeType MIME type to use when attaching file. If not provided, will attempt to infer via HTTP::get_mime_type().
* @return $this
*/
public function attachFile($filename, $attachedFilename = null, $mimeType = null) {
if(!$attachedFilename) $attachedFilename = basename($filename);
$absoluteFileName = Director::getAbsFile($filename);
if(file_exists($absoluteFileName)) {
$this->attachFileFromString(file_get_contents($absoluteFileName), $attachedFilename, $mimetype);
$this->attachFileFromString(file_get_contents($absoluteFileName), $attachedFilename, $mimeType);
} else {
user_error("Could not attach '$absoluteFileName' to email. File does not exist.", E_USER_NOTICE);
}
return $this;
}
/**
* @return string|null
*/
public function Subject() {
return $this->subject;
}
/**
* @return string|null
*/
public function Body() {
return $this->body;
}
/**
* @return string|null
*/
public function To() {
return $this->to;
}
/**
* @return string|null
*/
public function From() {
return $this->from;
}
/**
* @return string|null
*/
public function Cc() {
return $this->cc;
}
/**
* @return string|null
*/
public function Bcc() {
return $this->bcc;
}
/**
* @param string $val
* @return $this
*/
public function setSubject($val) {
$this->subject = $val;
return $this;
}
/**
* @param string $val
* @return $this
*/
public function setBody($val) {
$this->body = $val;
return $this;
}
/**
* @param string $val
* @return $this
*/
public function setTo($val) {
$this->to = $val;
return $this;
}
/**
* @param string $val
* @return $this
*/
public function setFrom($val) {
$this->from = $val;
return $this;
}
/**
* @param string $val
* @return $this
*/
public function setCc($val) {
$this->cc = $val;
return $this;
}
/**
* @param string $val
* @return $this
*/
public function setBcc($val) {
$this->bcc = $val;
return $this;
@ -251,36 +321,64 @@ class Email extends ViewableData {
/**
* Set the "Reply-To" header with an email address.
* @param string $email The email address of the "Reply-To" header
*
* @param string $val
* @return $this
*/
public function replyTo($email) {
$this->addCustomHeader('Reply-To', $email);
public function setReplyTo($val) {
$this->addCustomHeader('Reply-To', $val);
return $this;
}
/**
* Add a custom header to this value.
* Useful for implementing all those cool features that we didn't think of.
* @param string $email
* @return $this
* @deprecated 4.0 Use the "setReplyTo" method instead
*/
public function replyTo($email) {
Deprecation::notice('4.0', 'Use the "setReplyTo" method instead');
$this->setReplyTo($email);
return $this;
}
/**
* Add a custom header to this email message. Useful for implementing all those cool features that we didn't think of.
*
* IMPORTANT: If the specified header already exists, the provided value will be appended!
*
* @todo Should there be an option to replace instead of append? Or maybe a new method ->setCustomHeader()?
*
* @param string $headerName
* @param string $headerValue
* @return $this
*/
public function addCustomHeader($headerName, $headerValue) {
if($headerName == 'Cc') $this->cc = $headerValue;
else if($headerName == 'Bcc') $this->bcc = $headerValue;
else {
if(isset($this->customHeaders[$headerName])) $this->customHeaders[$headerName] .= ", " . $headerValue;
else $this->customHeaders[$headerName] = $headerValue;
if ($headerName == 'Cc') {
$this->cc = $headerValue;
} elseif($headerName == 'Bcc') {
$this->bcc = $headerValue;
} else {
// Append value instead of replacing.
if(isset($this->customHeaders[$headerName])) {
$this->customHeaders[$headerName] .= ", " . $headerValue;
} else {
$this->customHeaders[$headerName] = $headerValue;
}
}
return $this;
}
/**
* @return string
*/
public function BaseURL() {
return Director::absoluteBaseURL();
}
/**
* Debugging help
* Get an HTML string for debugging purposes.
*
* @return string
*/
public function debug() {
$this->parseVariables();
@ -298,6 +396,7 @@ class Email extends ViewableData {
* Set template name (without *.ss extension).
*
* @param string $template
* @return $this
*/
public function setTemplate($template) {
$this->ss_template = $template;
@ -311,6 +410,9 @@ class Email extends ViewableData {
return $this->ss_template;
}
/**
* @return $this
*/
protected function templateData() {
if($this->template_data) {
return $this->template_data->customise(array(
@ -336,8 +438,10 @@ class Email extends ViewableData {
}
/**
* Populate this email template with values.
* This may be called many times.
* Populate this email template with values. This may be called many times.
*
* @param array|ViewableData $data
* @return $this
*/
public function populateTemplate($data) {
if($this->template_data) {
@ -356,6 +460,9 @@ class Email extends ViewableData {
* the template into body. Called before send() or debugSend()
* $isPlain=true will cause the template to be ignored, otherwise the GenericEmail template will be used
* and it won't be plain email :)
*
* @param bool $isPlain
* @return $this
*/
protected function parseVariables($isPlain = false) {
$origState = Config::inst()->get('SSViewer', 'source_file_comments');
@ -389,15 +496,13 @@ class Email extends ViewableData {
}
/**
* Validates the email address. Returns true of false
* @param string $address
* @return bool
* @deprecated 4.0 Use the "is_valid_address" method instead
*/
public static function validEmailAddress($address) {
if (function_exists('filter_var')) {
return filter_var($address, FILTER_VALIDATE_EMAIL);
} else {
return preg_match('#^([a-zA-Z0-9_+\.\-]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)'
. '|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$#', $address);
}
Deprecation::notice('4.0', 'Use the "is_valid_address" method instead');
return static::is_valid_address($address);
}
/**
@ -407,8 +512,8 @@ class Email extends ViewableData {
* @uses Mailer->sendPlain()
*
* @param string $messageID Optional message ID so the message can be identified in bounces etc.
* @return bool Success of the sending operation from an MTA perspective.
* Doesn't actually give any indication if the mail has been delivered to the recipient properly)
* @return mixed Success of the sending operation from an MTA perspective. Doesn't actually give any indication if
* the mail has been delivered to the recipient properly). See Mailer->sendPlain() for return type details.
*/
public function sendPlain($messageID = null) {
Requirements::clear();
@ -472,8 +577,8 @@ class Email extends ViewableData {
* @uses Mailer->sendHTML()
*
* @param string $messageID Optional message ID so the message can be identified in bounces etc.
* @return bool Success of the sending operation from an MTA perspective.
* Doesn't actually give any indication if the mail has been delivered to the recipient properly)
* @return mixed Success of the sending operation from an MTA perspective. Doesn't actually give any indication if
* the mail has been delivered to the recipient properly). See Mailer->sendPlain() for return type details.
*/
public function send($messageID = null) {
Requirements::clear();
@ -609,10 +714,10 @@ class Email extends ViewableData {
}
/**
* Checks for RFC822-valid email format.
* Validates the email address to get as close to RFC 822 compliant as possible.
*
* @param string $str
* @return boolean
* @param string $email
* @return bool
*
* @copyright Cal Henderson <cal@iamcal.com>
* This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
@ -633,13 +738,12 @@ class Email extends ViewableData {
$local_part = "$word(\\x2e$word)*";
$addr_spec = "$local_part\\x40$domain";
return preg_match("!^$addr_spec$!", $email) ? 1 : 0;
return preg_match("!^$addr_spec$!", $email) === 1;
}
/**
* Encode an email-address to protect it from spambots.
* At the moment only simple string substitutions,
* which are not 100% safe from email harvesting.
* Encode an email-address to help protect it from spam bots. At the moment only simple string substitutions, which
* are not 100% safe from email harvesting.
*
* @todo Integrate javascript-based solution
*

View File

@ -55,27 +55,16 @@ class EmailTest extends SapphireTest {
$invalidEmails = array('foo.bar@', '@example.com', 'foo@');
foreach ($validEmails as $email) {
$this->assertEquals(
$email,
Email::validEmailAddress($email),
'validEmailAddress() returns a valid email address'
);
$this->assertEquals(
1,
$this->assertTrue(
Email::is_valid_address($email),
'is_valid_address() returns 1 for a valid email address'
'is_valid_address() returns true for a valid email address'
);
}
foreach ($invalidEmails as $email) {
$this->assertFalse(
Email::validEmailAddress($email),
'validEmailAddress() returns false for an invalid email address'
);
$this->assertEquals(
0,
Email::is_valid_address($email),
'is_valid_address() returns 0 for an invalid email address'
'is_valid_address() returns false for an invalid email address'
);
}
}