1 <?php
2
3 4 5 6
7
8 if(isset($_SERVER['SERVER_NAME'])) {
9 10 11
12 define('X_MAILER', 'SilverStripe Mailer - version 2006.06.21 (Sent from "'.$_SERVER['SERVER_NAME'].'")');
13 } else {
14 15 16
17 define('X_MAILER', 'SilverStripe Mailer - version 2006.06.21');
18 }
19
20
21 22 23 24 25
26 class Email extends ViewableData {
27
28 29 30
31 protected $from;
32
33 34 35
36 protected $to;
37
38 39 40
41 protected $subject;
42
43 44 45 46
47 protected $body;
48
49 50 51 52
53 protected $plaintext_body;
54
55 56 57
58 protected $cc;
59
60 61 62
63 protected $bcc;
64
65 66 67
68 protected static $mailer;
69
70 71 72 73 74
75 static function set_mailer(Mailer $mailer) {
76 self::$mailer = $mailer;
77 }
78
79 80 81 82 83
84 static function mailer() {
85 if(!self::$mailer) self::$mailer = new Mailer();
86 return self::$mailer;
87 }
88
89 90 91
92 protected = array();
93
94 95 96
97 protected $attachments = array();
98
99 100 101
102 protected $parseVariables_done = false;
103
104 105 106
107 protected $ss_template = "GenericEmail";
108
109 110 111 112
113 protected $template_data = null;
114
115 116 117
118 protected $bounceHandlerURL = null;
119
120 121 122 123
124 static $admin_email_address = '';
125
126 127 128
129 protected static $send_all_emails_to = null;
130
131 132 133
134 protected static $bcc_all_emails_to = null;
135
136 137 138
139 protected static $cc_all_emails_to = null;
140
141 142 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 234 235
236 public function replyTo($email) {
237 $this->addCustomHeader('Reply-To', $email);
238 }
239
240 241 242 243 244 245 246
247 public function ($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 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 277 278 279
280 public function setTemplate($template) {
281 $this->ss_template = $template;
282 }
283
284 285 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 310
311 public function IsEmail() {
312 return true;
313 }
314
315 316 317 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 331 332 333 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
342 $data = $this->templateData();
343
344
345 $fullBody = $this->body;
346 if ($this->getTemplate()) {
347
348 $template = new SSViewer(($isPlain) ? $this->getTemplate() . '_plain' : $this->getTemplate());
349 if ($template->exists()) {
350
351 $fullBody = $template->process($this->templateData());
352 }
353 }
354
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 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 372 373 374 375 376 377 378 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 434 435 436 437 438 439 440 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 498 499 500 501 502 503 504
505 public static function setAdminEmail($newEmail) {
506 self::$admin_email_address = $newEmail;
507 }
508
509 510 511
512 public static function getAdminEmail() {
513 return self::$admin_email_address;
514 }
515
516 517 518 519 520 521 522
523 public static function send_all_emails_to($emailAddress) {
524 self::$send_all_emails_to = $emailAddress;
525 }
526
527 528 529 530 531 532 533 534 535 536
537 public static function cc_all_emails_to($emailAddress) {
538 self::$cc_all_emails_to = $emailAddress;
539 }
540
541 542 543 544 545 546 547 548 549 550
551 public static function bcc_all_emails_to($emailAddress) {
552 self::$bcc_all_emails_to = $emailAddress;
553 }
554
555 556 557 558 559 560 561 562 563 564 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 586 587 588 589 590 591 592 593 594 595 596 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 622 623 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
644 if( !isset($_REQUEST['Key']) ) {
645 echo 'Error: Access validation failed. No "Key" specified.';
646 return;
647 }
648
649
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
680
681 if( isset($_REQUEST['SilverStripeMessageID'])) {
682
683 $message_id_parts = explode('.', $_REQUEST['SilverStripeMessageID']);
684
685 $newsletter_id_date_parts = explode ('_', base64_decode($message_id_parts[1]) );
686
687
688 $SQL_memberID = Convert::raw2sql($member->ID);
689 $SQL_newsletterID = Convert::raw2sql($newsletter_id_date_parts[0]);
690
691
692 $oldNewsletterSentRecipient = DataObject::get_one("Newsletter_SentRecipient", "\"MemberID\" = '$SQL_memberID' AND \"ParentID\" = '$SQL_newsletterID' AND \"Email\" = '$SQL_email'");
693
694
695 if($oldNewsletterSentRecipient) {
696 $oldNewsletterSentRecipient->Result = 'Bounced';
697 $oldNewsletterSentRecipient->write();
698 } else {
699
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
709
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 718
719
720 if( !$time )
721 $time = date( 'H:i:s' );
722 723
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 740 741 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 765 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.
-