Version: current

      1 <?php
      2
      3 /**
      4  * @package sapphire
      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 // Note: The constant 'BOUNCE_EMAIL' should be defined as a valid email address for where bounces should be returned to.
     20
     21 /**
     22  * Class to support sending emails.
     23  * @package sapphire
     24  * @subpackage email
     25  */
     26 class Email extends ViewableData {
     27
     28     /**
     29      * @param string $from Email-Address
     30      */
     31     protected $from;
     32
     33     /**
     34      * @param string $to Email-Address. Use comma-separation to pass multiple email-addresses.
     35      */
     36     protected $to;
     37
     38     /**
     39      * @param string $subject Subject of the email
     40      */
     41     protected $subject;
     42
     43     /**
     44      * @param string $body HTML content of the email.
     45      * Passed straight into {@link $ss_template} as $Body variable.
     46      */
     47     protected $body;
     48
     49     /**
     50      * @param string $plaintext_body Optional string for plaintext emails.
     51      * If not set, defaults to converting the HTML-body with {@link Convert::xml2raw()}.
     52      */
     53     protected $plaintext_body;
     54
     55     /**
     56      * @param string $cc
     57      */
     58     protected $cc;
     59
     60     /**
     61      * @param string $bcc
     62      */
     63     protected $bcc;
     64
     65     /**
     66      * @param Mailer $mailer Instance of a {@link Mailer} class.
     67      */
     68     protected static $mailer;
     69
     70     /**
     71      * This can be used to provide a mailer class other than the default, e.g. for testing.
     72      *
     73      * @param Mailer $mailer
     74      */
     75     static function set_mailer(Mailer $mailer) {
     76         self::$mailer = $mailer;
     77     }
     78
     79     /**
     80      * Get the mailer.
     81      *
     82      * @return Mailer
     83      */
     84     static function mailer() {
     85         if(!self::$mailer) self::$mailer = new Mailer();
     86         return self::$mailer;
     87     }
     88
     89     /**
     90      * @param array $customHeaders A map of header-name -> header-value
     91      */
     92     protected $customHeaders = array();
     93
     94     /**
     95      * @param array $attachements Internal, use {@link attachFileFromString()} or {@link attachFile()}
     96      */
     97     protected $attachments = array();
     98
     99     /**
    100      * @param boolean $
    101      */
    102     protected $parseVariables_done = false;
    103
    104     /**
    105      * @param string $ss_template The name of the used template (without *.ss extension)
    106      */
    107     protected $ss_template = "GenericEmail";
    108
    109     /**
    110      * @param array $template_data Additional data available in a template.
    111      * Used in the same way than {@link ViewableData->customize()}.
    112      */
    113     protected $template_data = null;
    114
    115     /**
    116      * @param string $bounceHandlerURL
    117      */
    118     protected $bounceHandlerURL = null;
    119
    120     /**
    121      * @param sring $admin_email_address The default administrator email address.
    122      * This will be set in the config on a site-by-site basis
    123      */
    124     static $admin_email_address = '';
    125
    126     /**
    127      * @param string $send_all_emails_to Email-Address
    128      */
    129     protected static $send_all_emails_to = null;
    130
    131     /**
    132      * @param string $bcc_all_emails_to Email-Address
    133      */
    134     protected static $bcc_all_emails_to = null;
    135
    136     /**
    137      * @param string $cc_all_emails_to Email-Address
    138      */
    139     protected static $cc_all_emails_to = null;
    140
    141     /**
    142      * Create a new email.
    143      */
    144     public function __construct($from = null, $to = null, $subject = null, $body = null, $bounceHandlerURL = null, $cc = null, $bcc = null) {
    145         if($from != null) $this->from = $from;
    146         if($to != null) $this->to = $to;
    147         if($subject != null) $this->subject = $subject;
    148         if($body != null) $this->body = $body;
    149         if($cc != null) $this->cc = $cc;
    150         if($bcc != null) $this->bcc = $bcc;
    151         if($bounceHandlerURL != null) $this->setBounceHandlerURL($bounceHandlerURL);
    152         parent::__construct();
    153     }
    154
    155     public function attachFileFromString($data, $filename, $mimetype = null) {
    156         $this->attachments[] = array(
    157             'contents' => $data,
    158             'filename' => $filename,
    159             'mimetype' => $mimetype,
    160         );
    161     }
    162
    163     public function setBounceHandlerURL( $bounceHandlerURL ) {
    164         if($bounceHandlerURL) {
    165             $this->bounceHandlerURL = $bounceHandlerURL;
    166         } else {
    167             $this->bounceHandlerURL = $_SERVER['HTTP_HOST'] . Director::baseURL() . 'Email_BounceHandler';
    168         }
    169     }
    170
    171     public function attachFile($filename, $attachedFilename = null, $mimetype = null) {
    172         $absoluteFileName = Director::getAbsFile($filename);
    173         if(file_exists($absoluteFileName)) {
    174             $this->attachFileFromString(file_get_contents($absoluteFileName), $attachedFilename, $mimetype);
    175         } else {
    176             user_error("Could not attach '$absoluteFileName' to email. File does not exist.", E_USER_NOTICE);
    177         }
    178     }
    179
    180     public function Subject() {
    181         return $this->subject;
    182     }
    183
    184     public function Body() {
    185         return $this->body;
    186     }
    187
    188     public function To() {
    189         return $this->to;
    190     }
    191
    192     public function From() {
    193         return $this->from;
    194     }
    195
    196     public function Cc() {
    197         return $this->cc;
    198     }
    199
    200     public function Bcc() {
    201         return $this->bcc;
    202     }
    203
    204     public function setSubject($val) {
    205         $this->subject = $val;
    206     }
    207
    208     public function setBody($val) {
    209         $this->body = $val;
    210     }
    211
    212     public function setPlainBody($val) {
    213         $this->plaintext_body = $val;
    214     }
    215
    216     public function setTo($val) {
    217         $this->to = $val;
    218     }
    219
    220     public function setFrom($val) {
    221         $this->from = $val;
    222     }
    223
    224     public function setCc($val) {
    225         $this->cc = $val;
    226     }
    227
    228     public function setBcc($val) {
    229         $this->bcc = $val;
    230     }
    231
    232     /**
    233      * Set the "Reply-To" header with an email address.
    234      * @param string $email The email address of the "Reply-To" header
    235      */
    236     public function replyTo($email) {
    237         $this->addCustomHeader('Reply-To', $email);
    238     }
    239
    240     /**
    241      * Add a custom header to this value.
    242      * Useful for implementing all those cool features that we didn't think of.
    243      *
    244      * @param string $headerName
    245      * @param string $headerValue
    246      */
    247     public function addCustomHeader($headerName, $headerValue) {
    248         if($headerName == 'Cc') $this->cc = $headerValue;
    249         else if($headerName == 'Bcc') $this->bcc = $headerValue;
    250         else {
    251             if(isset($this->customHeaders[$headerName])) $this->customHeaders[$headerName] .= ", " . $headerValue;
    252             else $this->customHeaders[$headerName] = $headerValue;
    253         }
    254     }
    255
    256     public function BaseURL() {
    257         return Director::absoluteBaseURL();
    258     }
    259
    260     /**
    261      * Debugging help
    262      */
    263     public function debug() {
    264         $this->parseVariables();
    265
    266         return "<h2>Email template $this->class</h2>\n" .
    267             "<p><b>From:</b> $this->from\n" .
    268             "<b>To:</b> $this->to\n" .
    269             "<b>Cc:</b> $this->cc\n" .
    270             "<b>Bcc:</b> $this->bcc\n" .
    271             "<b>Subject:</b> $this->subject</p>" .
    272             $this->body;
    273     }
    274
    275     /**
    276      * Set template name (without *.ss extension).
    277      *
    278      * @param string $template
    279      */
    280     public function setTemplate($template) {
    281         $this->ss_template = $template;
    282     }
    283
    284     /**
    285      * @return string
    286      */
    287     public function getTemplate() {
    288         return $this->ss_template;
    289     }
    290
    291     protected function templateData() {
    292         if($this->template_data) {
    293             return $this->template_data->customise(array(
    294                 "To" => $this->to,
    295                 "Cc" => $this->cc,
    296                 "Bcc" => $this->bcc,
    297                 "From" => $this->from,
    298                 "Subject" => $this->subject,
    299                 "Body" => $this->body,
    300                 "BaseURL" => $this->BaseURL(),
    301                 "IsEmail" => true,
    302             ));
    303         } else {
    304             return $this;
    305         }
    306     }
    307
    308     /**
    309      * Used by {@link SSViewer} templates to detect if we're rendering an email template rather than a page template
    310      */
    311     public function IsEmail() {
    312         return true;
    313     }
    314
    315     /**
    316      * Populate this email template with values.
    317      * This may be called many times.
    318      */
    319     function populateTemplate($data) {
    320         if($this->template_data) {
    321             $this->template_data = $this->template_data->customise($data);
    322         } else {
    323             if(is_array($data)) $data = new ArrayData($data);
    324             $this->template_data = $this->customise($data);
    325         }
    326         $this->parseVariables_done = false;
    327     }
    328
    329     /**
    330      * Load all the template variables into the internal variables, including
    331      * the template into body.  Called before send() or debugSend()
    332      * $isPlain=true will cause the template to be ignored, otherwise the GenericEmail template will be used
    333      * and it won't be plain email :)
    334      */
    335     protected function parseVariables($isPlain = false) {
    336         SSViewer::set_source_file_comments(false);
    337
    338         if(!$this->parseVariables_done) {
    339             $this->parseVariables_done = true;
    340
    341             // Parse $ variables in the base parameters
    342             $data = $this->templateData();
    343
    344             // Process a .SS template file
    345             $fullBody = $this->body;
    346             if ($this->getTemplate()) {
    347
    348                 $template = new SSViewer(($isPlain) ? $this->getTemplate() . '_plain' : $this->getTemplate());
    349                 if ($template->exists()) {
    350                 // Requery data so that updated versions of To, From, Subject, etc are included
    351                     $fullBody = $template->process($this->templateData());
    352                 }
    353             }
    354             // Rewrite relative URLs
    355             $this->body = HTTP::absoluteURLs($fullBody);
    356         }
    357     }
    358
    359     function renderWith($template, $customFields = null) {
    360         return $this->templateData()->renderWith($template, $customFields);
    361     }
    362
    363     /**
    364      * @desc Validates the email address. Returns true of false
    365      */
    366     static function validEmailAddress($address) {
    367         return ereg('^([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);
    368     }
    369
    370     /**
    371      * Send the email in plaintext.
    372      *
    373      * @see send() for sending emails with HTML content.
    374      * @uses Mailer->sendPlain()
    375      *
    376      * @param string $messageID Optional message ID so the message can be identified in bounces etc.
    377      * @return bool Success of the sending operation from an MTA perspective.
    378      * Doesn't actually give any indication if the mail has been delivered to the recipient properly)
    379      */
    380     function sendPlain($messageID = null) {
    381         Requirements::clear();
    382
    383         $this->parseVariables(true);
    384
    385         if(empty($this->from)) $this->from = Email::getAdminEmail();
    386
    387         $this->setBounceHandlerURL($this->bounceHandlerURL);
    388
    389         $headers = $this->customHeaders;
    390
    391         $headers['X-SilverStripeBounceURL'] = $this->bounceHandlerURL;
    392
    393         if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID;
    394
    395         if(@$_SERVER['REMOTE_ADDR'] && $_SERVER['REMOTE_ADDR'] != '127.0.0.1')
    396             $headers['X-SilverStripeClientIP'] = $_SERVER['REMOTE_ADDR'];
    397
    398         $to = $this->to;
    399         $subject = $this->subject;
    400         if(self::$send_all_emails_to) {
    401             $subject .= " [addressed to $to";
    402             $to = self::$send_all_emails_to;
    403             if($this->cc) $subject .= ", cc to $this->cc";
    404             if($this->bcc) $subject .= ", bcc to $this->bcc";
    405             $subject .= ']';
    406         } else {
    407             if($this->cc) $headers['Cc'] = $this->cc;
    408             if($this->bcc) $headers['Bcc'] = $this->bcc;
    409         }
    410
    411         if(self::$cc_all_emails_to) {
    412             if(!empty($headers['Cc']) && trim($headers['Cc'])) {
    413                 $headers['Cc'] .= ', ' . self::$cc_all_emails_to;
    414             } else {
    415                 $headers['Cc'] = self::$cc_all_emails_to;
    416             }
    417         }
    418
    419         if(self::$bcc_all_emails_to) {
    420             if(!empty($headers['Bcc']) && trim($headers['Bcc'])) {
    421                 $headers['Bcc'] .= ', ' . self::$bcc_all_emails_to;
    422             } else {
    423                 $headers['Bcc'] = self::$bcc_all_emails_to;
    424             }
    425         }
    426
    427         Requirements::restore();
    428
    429         return self::mailer()->sendPlain($to, $this->from, $subject, ($this->plaintext_body) ? $this->plaintext_body : $this->body, $this->attachments, $headers);
    430     }
    431
    432     /**
    433      * Send an email with HTML content.
    434      *
    435      * @see sendPlain() for sending plaintext emails only.
    436      * @uses Mailer->sendHTML()
    437      *
    438      * @param string $messageID Optional message ID so the message can be identified in bounces etc.
    439      * @return bool Success of the sending operation from an MTA perspective.
    440      * Doesn't actually give any indication if the mail has been delivered to the recipient properly)
    441      */
    442     public function send($messageID = null) {
    443         Requirements::clear();
    444
    445         $this->parseVariables();
    446
    447         if(empty($this->from)) $this->from = Email::getAdminEmail();
    448
    449         $this->setBounceHandlerURL( $this->bounceHandlerURL );
    450
    451         $headers = $this->customHeaders;
    452
    453         $headers['X-SilverStripeBounceURL'] = $this->bounceHandlerURL;
    454
    455         if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID;
    456
    457         if(@$_SERVER['REMOTE_ADDR'] && $_SERVER['REMOTE_ADDR'] != '127.0.0.1')
    458             $headers['X-SilverStripeClientIP'] = $_SERVER['REMOTE_ADDR'];
    459
    460         $to = $this->to;
    461         $subject = $this->subject;
    462         if(self::$send_all_emails_to) {
    463             $subject .= " [addressed to $to";
    464             $to = self::$send_all_emails_to;
    465             if($this->cc) $subject .= ", cc to $this->cc";
    466             if($this->bcc) $subject .= ", bcc to $this->bcc";
    467             $subject .= ']';
    468             unset($headers['Cc']);
    469             unset($headers['Bcc']);
    470         } else {
    471             if($this->cc) $headers['Cc'] = $this->cc;
    472             if($this->bcc) $headers['Bcc'] = $this->bcc;
    473         }
    474
    475         if(self::$cc_all_emails_to) {
    476             if(!empty($headers['Cc']) && trim($headers['Cc'])) {
    477                 $headers['Cc'] .= ', ' . self::$cc_all_emails_to;
    478             } else {
    479                 $headers['Cc'] = self::$cc_all_emails_to;
    480             }
    481         }
    482
    483         if(self::$bcc_all_emails_to) {
    484             if(!empty($headers['Bcc']) && trim($headers['Bcc'])) {
    485                 $headers['Bcc'] .= ', ' . self::$bcc_all_emails_to;
    486             } else {
    487                 $headers['Bcc'] = self::$bcc_all_emails_to;
    488             }
    489         }
    490
    491         Requirements::restore();
    492
    493         return self::mailer()->sendHTML($to, $this->from, $subject, $this->body, $this->attachments, $headers, $this->plaintext_body);
    494     }
    495
    496     /**
    497      * Used as a default sender address in the {@link Email} class
    498      * unless overwritten. Also shown to users on live environments
    499      * as a contact address on system error pages.
    500      *
    501      * Used by {@link Email->send()}, {@link Email->sendPlain()}, {@link Debug->friendlyError()}.
    502      *
    503      * @param string $newEmail
    504      */
    505     public static function setAdminEmail($newEmail) {
    506         self::$admin_email_address = $newEmail;
    507     }
    508
    509     /**
    510      * @return string
    511      */
    512     public static function getAdminEmail() {
    513         return self::$admin_email_address;
    514     }
    515
    516     /**
    517      * Send every email generated by the Email class to the given address.
    518      * It will also add " [addressed to (email), cc to (email), bcc to (email)]" to the end of the subject line
    519      * This can be used when testing, by putting a command like this in your _config.php file
    520      *
    521      * if(!Director::isLive()) Email::send_all_emails_to("someone@example.com")
    522      */
    523     public static function send_all_emails_to($emailAddress) {
    524         self::$send_all_emails_to = $emailAddress;
    525     }
    526
    527     /**
    528      * CC every email generated by the Email class to the given address.
    529      * It won't affect the original delivery in the same way that send_all_emails_to does.  It just adds a CC header
    530      * with the given email address.    Note that you can only call this once - subsequent calls will overwrite the configuration
    531      * variable.
    532      *
    533      * This can be used when you have a system that relies heavily on email and you want someone to be checking all correspondence.
    534      *
    535      * if(Director::isLive()) Email::cc_all_emails_to("supportperson@example.com")
    536      */
    537     public static function cc_all_emails_to($emailAddress) {
    538         self::$cc_all_emails_to = $emailAddress;
    539     }
    540
    541     /**
    542      * BCC every email generated by the Email class to the given address.
    543      * It won't affect the original delivery in the same way that send_all_emails_to does.  It just adds a BCC header
    544      * with the given email address.    Note that you can only call this once - subsequent calls will overwrite the configuration
    545      * variable.
    546      *
    547      * This can be used when you have a system that relies heavily on email and you want someone to be checking all correspondence.
    548      *
    549      * if(Director::isLive()) Email::cc_all_emails_to("supportperson@example.com")
    550      */
    551     public static function bcc_all_emails_to($emailAddress) {
    552         self::$bcc_all_emails_to = $emailAddress;
    553     }
    554
    555     /**
    556      * Checks for RFC822-valid email format.
    557      *
    558      * @param string $str
    559      * @return boolean
    560      *
    561      * @see http://code.iamcal.com/php/rfc822/rfc822.phps
    562      * @copyright Cal Henderson <cal@iamcal.com>
    563      *  This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
    564      *  http://creativecommons.org/licenses/by-sa/2.5/
    565      */
    566     function is_valid_address($email){
    567         $qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
    568         $dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
    569         $atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c'.
    570             '\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
    571         $quoted_pair = '\\x5c[\\x00-\\x7f]';
    572         $domain_literal = "\\x5b($dtext|$quoted_pair)*\\x5d";
    573         $quoted_string = "\\x22($qtext|$quoted_pair)*\\x22";
    574         $domain_ref = $atom;
    575         $sub_domain = "($domain_ref|$domain_literal)";
    576         $word = "($atom|$quoted_string)";
    577         $domain = "$sub_domain(\\x2e$sub_domain)*";
    578         $local_part = "$word(\\x2e$word)*";
    579         $addr_spec = "$local_part\\x40$domain";
    580
    581         return preg_match("!^$addr_spec$!", $email) ? 1 : 0;
    582     }
    583
    584     /**
    585      * Encode an email-address to protect it from spambots.
    586      * At the moment only simple string substitutions,
    587      * which are not 100% safe from email harvesting.
    588      *
    589      * @todo Integrate javascript-based solution
    590      *
    591      * @param string $email Email-address
    592      * @param string $method Method for obfuscating/encoding the address
    593      *  - 'direction': Reverse the text and then use CSS to put the text direction back to normal
    594      *  - 'visible': Simple string substitution ('@' to '[at]', '.' to '[dot], '-' to [dash])
    595      *  - 'hex': Hexadecimal URL-Encoding - useful for mailto: links
    596      * @return string
    597      */
    598     public static function obfuscate($email, $method = 'visible') {
    599         switch($method) {
    600             case 'direction' :
    601                 Requirements::customCSS(
    602                     'span.codedirection { unicode-bidi: bidi-override; direction: rtl; }',
    603                     'codedirectionCSS'
    604                 );
    605                 return '<span class="codedirection">' . strrev($email) . '</span>';
    606             case 'visible' :
    607                 $obfuscated = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
    608                 return strtr($email, $obfuscated);
    609             case 'hex' :
    610                 $encoded = '';
    611                 for ($x=0; $x < strlen($email); $x++) $encoded .= '&#x' . bin2hex($email{$x}).';';
    612                 return $encoded;
    613             default:
    614                 user_error('Email::obfuscate(): Unknown obfuscation method', E_USER_NOTICE);
    615                 return $email;
    616         }
    617     }
    618 }
    619
    620 /**
    621  * Base class that email bounce handlers extend
    622  * @package sapphire
    623  * @subpackage email
    624  */
    625 class Email_BounceHandler extends Controller {
    626
    627     function init() {
    628         BasicAuth::protect_entire_site(false);
    629         parent::init();
    630     }
    631
    632     function index() {
    633         $subclasses = ClassInfo::subclassesFor( $this->class );
    634         unset($subclasses[$this->class]);
    635
    636         if( $subclasses ) {
    637             $subclass = array_pop( $subclasses );
    638             $task = new $subclass();
    639             $task->index();
    640             return;
    641         }
    642
    643         // Check if access key exists
    644         if( !isset($_REQUEST['Key']) ) {
    645             echo 'Error: Access validation failed. No "Key" specified.';
    646             return;
    647         }
    648
    649         // Check against access key defined in sapphire/_config.php
    650         if( $_REQUEST['Key'] != EMAIL_BOUNCEHANDLER_KEY) {
    651             echo 'Error: Access validation failed. Invalid "Key" specified.';
    652             return;
    653         }
    654
    655         if( !$_REQUEST['Email'] ) {
    656             echo "No email address";
    657             return;
    658         }
    659
    660         $this->recordBounce( $_REQUEST['Email'], $_REQUEST['Date'], $_REQUEST['Time'], $_REQUEST['Message'] );
    661     }
    662
    663     private function recordBounce( $email, $date = null, $time = null, $error = null ) {
    664         if(ereg('<(.*)>', $email, $parts)) $email = $parts[1];
    665
    666         $SQL_email = Convert::raw2sql($email);
    667         $SQL_bounceTime = Convert::raw2sql("$date $time");
    668
    669         $duplicateBounce = DataObject::get_one("Email_BounceRecord", "\"BounceEmail\" = '$SQL_email' AND (\"BounceTime\"+INTERVAL 1 MINUTE) > '$SQL_bounceTime'");
    670
    671         if(!$duplicateBounce) {
    672             $record = new Email_BounceRecord();
    673
    674             $member = DataObject::get_one( 'Member', "\"Email\"='$SQL_email'" );
    675
    676             if( $member ) {
    677                 $record->MemberID = $member->ID;
    678
    679                 // If the SilverStripeMessageID (taken from the X-SilverStripeMessageID header embedded in the email) is sent,
    680                 // then log this bounce in a Newsletter_SentRecipient record so it will show up on the 'Sent Status Report' tab of the Newsletter
    681                 if( isset($_REQUEST['SilverStripeMessageID'])) {
    682                     // Note: was sent out with: $project . '.' . $messageID;
    683                     $message_id_parts = explode('.', $_REQUEST['SilverStripeMessageID']);
    684                     // Note: was encoded with: base64_encode( $newsletter->ID . '_' . date( 'd-m-Y H:i:s' ) );
    685                     $newsletter_id_date_parts = explode ('_', base64_decode($message_id_parts[1]) );
    686
    687                     // Escape just in case
    688                     $SQL_memberID = Convert::raw2sql($member->ID);
    689                     $SQL_newsletterID = Convert::raw2sql($newsletter_id_date_parts[0]);
    690
    691                     // Log the bounce
    692                     $oldNewsletterSentRecipient = DataObject::get_one("Newsletter_SentRecipient", "\"MemberID\" = '$SQL_memberID' AND \"ParentID\" = '$SQL_newsletterID' AND \"Email\" = '$SQL_email'");
    693
    694                     // Update the Newsletter_SentRecipient record if it exists
    695                     if($oldNewsletterSentRecipient) {
    696                         $oldNewsletterSentRecipient->Result = 'Bounced';
    697                         $oldNewsletterSentRecipient->write();
    698                     } else {
    699                         // For some reason it didn't exist, create a new record
    700                         $newNewsletterSentRecipient = new Newsletter_SentRecipient();
    701                         $newNewsletterSentRecipient->Email = $SQL_email;
    702                         $newNewsletterSentRecipient->MemberID = $member->ID;
    703                         $newNewsletterSentRecipient->Result = 'Bounced';
    704                         $newNewsletterSentRecipient->ParentID = $newsletter_id_date_parts[0];
    705                         $newNewsletterSentRecipient->write();
    706                     }
    707
    708                     // Now we are going to Blacklist this member so that email will not be sent to them in the future.
    709                     // Note: Sending can be re-enabled by going to 'Mailing List' 'Bounced' tab and unchecking the box under 'Blacklisted'
    710                     $member->setBlacklistedEmail(TRUE);
    711                     echo '<p><b>Member: '.$member->FirstName.' '.$member->Surname.' <'.$member->Email.'> was added to the Email Blacklist!</b></p>';
    712                 }
    713             }
    714
    715             if( !$date )
    716                     $date = date( 'd-m-Y' );
    717             /*else
    718                     $date = date( 'd-m-Y', strtotime( $date ) );*/
    719
    720             if( !$time )
    721                     $time = date( 'H:i:s' );
    722             /*else
    723                     $time = date( 'H:i:s', strtotime( $time ) );*/
    724
    725             $record->BounceEmail = $email;
    726             $record->BounceTime = $date . ' ' . $time;
    727             $record->BounceMessage = $error;
    728             $record->write();
    729
    730             echo "Handled bounced email to address: $email";
    731         } else {
    732             echo 'Sorry, this bounce report has already been logged, not logging this duplicate bounce.';
    733         }
    734     }
    735
    736 }
    737
    738 /**
    739  * Database record for recording a bounced email
    740  * @package sapphire
    741  * @subpackage email
    742  */
    743 class Email_BounceRecord extends DataObject {
    744     static $db = array(
    745             'BounceEmail' => 'Varchar',
    746             'BounceTime' => 'SS_Datetime',
    747             'BounceMessage' => 'Varchar'
    748     );
    749
    750     static $has_one = array(
    751             'Member' => 'Member'
    752     );
    753
    754     static $has_many = array();
    755
    756     static $many_many = array();
    757
    758     static $defaults = array();
    759
    760     static $singular_name = 'Email Bounce Record';
    761
    762
    763     /**
    764     * a record of Email_BounceRecord can't be created manually. Instead, it should be
    765     * created though system.
    766     */
    767     public function canCreate($member = null) {
    768         return false;
    769     }
    770 }
    771
    772 ?>
    773

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -