Skip to content

Commit

Permalink
API Moved email bounce handling to new 'emailbouncehandler' module
Browse files Browse the repository at this point in the history
  • Loading branch information
chillu committed Dec 12, 2012
1 parent 0f60ca7 commit 5fed5b9
Show file tree
Hide file tree
Showing 4 changed files with 13 additions and 203 deletions.
14 changes: 1 addition & 13 deletions _config.php
Expand Up @@ -34,18 +34,6 @@
ShortcodeParser::get('default')->register('file_link', array('File', 'link_shortcode_handler'));
ShortcodeParser::get('default')->register('embed', array('Oembed', 'handle_shortcode'));

/**
* The secret key that needs to be sent along with pings to /Email_BounceHandler
*
* Change this to something different for increase security (you can
* override it in mysite/_config.php to ease upgrades).
* For more information see:
* {@link http://doc.silverstripe.org/doku.php?id=email_bouncehandler}
*/
if(!defined('EMAIL_BOUNCEHANDLER_KEY')) {
define('EMAIL_BOUNCEHANDLER_KEY', '1aaaf8fb60ea253dbf6efa71baaacbb3');
}

// Zend_Cache temp directory setting
$_ENV['TMPDIR'] = TEMP_FOLDER; // for *nix
$_ENV['TMP'] = TEMP_FOLDER; // for Windows
Expand All @@ -60,4 +48,4 @@
Deprecation::notification_version('3.0.0');

// TODO Remove once new ManifestBuilder with submodule support is in place
require_once('admin/_config.php');
require_once('admin/_config.php');
6 changes: 5 additions & 1 deletion docs/en/changelogs/3.1.0.md
Expand Up @@ -14,6 +14,10 @@
* Removed `Member_ProfileForm`, use `CMSProfileController` instead
* `SiteTree::$nested_urls` enabled by default. To disable, call `SiteTree::disable_nested_urls()`.
* Removed CMS permission checks from `File->canEdit()` and `File->canDelete()`. If you have unsecured controllers relying on these permissions, please override them through a `DataExtension`.
* Moved email bounce handling to new ["emailbouncehandler" module](https://github.com/silverstripe-labs/silverstripe-emailbouncehandler),
including `Email_BounceHandler` and `Email_BounceRecord` classes,
as well as the `Member->Bounced` property.
* Deprecated global email methods `htmlEmail()` and `plaintextEmail`, as well as various email helper methods like `encodeMultipart()`. Use the `Email` API, or the `Mailer` class where applicable.
* Removed non-functional `$inlineImages` option for sending emails * Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object
* Removed non-functional `$inlineImages` option for sending emails
* Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object
to populate the list instead (see [API docs](api:SelectionGroup)).
193 changes: 7 additions & 186 deletions email/Email.php
Expand Up @@ -16,8 +16,6 @@
*/
define('X_MAILER', 'SilverStripe Mailer - version 2006.06.21');
}
// Note: The constant 'BOUNCE_EMAIL' should be defined as a valid email address for where bounces should be returned
// to.

/**
* Class to support sending emails.
Expand Down Expand Up @@ -113,11 +111,6 @@ public static function mailer() {
*/
protected $template_data = null;

/**
* @param string $bounceHandlerURL
*/
protected $bounceHandlerURL = null;

/**
* @param sring $admin_email_address The default administrator email address.
* This will be set in the config on a site-by-site basis
Expand Down Expand Up @@ -151,7 +144,11 @@ public function __construct($from = null, $to = null, $subject = null, $body = n
if($body != null) $this->body = $body;
if($cc != null) $this->cc = $cc;
if($bcc != null) $this->bcc = $bcc;
if($bounceHandlerURL != null) $this->setBounceHandlerURL($bounceHandlerURL);

if($bounceHandlerURL != null) {
Deprecation::notice('3.1', 'Use "emailbouncehandler" module');
}

parent::__construct();
}

Expand All @@ -163,12 +160,8 @@ public function attachFileFromString($data, $filename, $mimetype = null) {
);
}

public function setBounceHandlerURL( $bounceHandlerURL ) {
if($bounceHandlerURL) {
$this->bounceHandlerURL = $bounceHandlerURL;
} else {
$this->bounceHandlerURL = $_SERVER['HTTP_HOST'] . Director::baseURL() . 'Email_BounceHandler';
}
public function setBounceHandlerURL($bounceHandlerURL) {
Deprecation::notice('3.1', 'Use "emailbouncehandler" module');
}

public function attachFile($filename, $attachedFilename = null, $mimetype = null) {
Expand Down Expand Up @@ -388,12 +381,8 @@ public function sendPlain($messageID = null) {

if(empty($this->from)) $this->from = Email::getAdminEmail();

$this->setBounceHandlerURL($this->bounceHandlerURL);

$headers = $this->customHeaders;

$headers['X-SilverStripeBounceURL'] = $this->bounceHandlerURL;

if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID;

if(project()) $headers['X-SilverStripeSite'] = project();
Expand Down Expand Up @@ -449,12 +438,8 @@ public function send($messageID = null) {

if(empty($this->from)) $this->from = Email::getAdminEmail();

$this->setBounceHandlerURL( $this->bounceHandlerURL );

$headers = $this->customHeaders;

$headers['X-SilverStripeBounceURL'] = $this->bounceHandlerURL;

if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID;

if(project()) $headers['X-SilverStripeSite'] = project();
Expand Down Expand Up @@ -620,167 +605,3 @@ public static function obfuscate($email, $method = 'visible') {
}
}
}

/**
* Base class that email bounce handlers extend
* @package framework
* @subpackage email
*/
class Email_BounceHandler extends Controller {

static $allowed_actions = array(
'index'
);

public function init() {
BasicAuth::protect_entire_site(false);
parent::init();
}

public function index() {
$subclasses = ClassInfo::subclassesFor( $this->class );
unset($subclasses[$this->class]);

if( $subclasses ) {
$subclass = array_pop( $subclasses );
$task = new $subclass();
$task->index();
return;
}

// Check if access key exists
if( !isset($_REQUEST['Key']) ) {
echo 'Error: Access validation failed. No "Key" specified.';
return;
}

// Check against access key defined in framework/_config.php
if( $_REQUEST['Key'] != EMAIL_BOUNCEHANDLER_KEY) {
echo 'Error: Access validation failed. Invalid "Key" specified.';
return;
}

if( !$_REQUEST['Email'] ) {
echo "No email address";
return;
}

$this->recordBounce( $_REQUEST['Email'], $_REQUEST['Date'], $_REQUEST['Time'], $_REQUEST['Message'] );
}

private function recordBounce( $email, $date = null, $time = null, $error = null ) {
if(preg_match('/<(.*)>/', $email, $parts)) $email = $parts[1];

$SQL_email = Convert::raw2sql($email);
$SQL_bounceTime = Convert::raw2sql("$date $time");

$duplicateBounce = DataObject::get_one("Email_BounceRecord",
"\"BounceEmail\" = '$SQL_email' AND (\"BounceTime\"+INTERVAL 1 MINUTE) > '$SQL_bounceTime'");

if(!$duplicateBounce) {
$record = new Email_BounceRecord();

$member = DataObject::get_one( 'Member', "\"Email\"='$SQL_email'" );

if( $member ) {
$record->MemberID = $member->ID;

// If the SilverStripeMessageID (taken from the X-SilverStripeMessageID header embedded in the email)
// is sent, then log this bounce in a Newsletter_SentRecipient record so it will show up on the 'Sent
// Status Report' tab of the Newsletter
if( isset($_REQUEST['SilverStripeMessageID'])) {
// Note: was sent out with: $project . '.' . $messageID;
$message_id_parts = explode('.', $_REQUEST['SilverStripeMessageID']);
// Note: was encoded with: base64_encode( $newsletter->ID . '_' . date( 'd-m-Y H:i:s' ) );
$newsletter_id_date_parts = explode ('_', base64_decode($message_id_parts[1]) );

// Escape just in case
$SQL_memberID = Convert::raw2sql($member->ID);
$SQL_newsletterID = Convert::raw2sql($newsletter_id_date_parts[0]);

// Log the bounce
$oldNewsletterSentRecipient = DataObject::get_one("Newsletter_SentRecipient",
"\"MemberID\" = '$SQL_memberID' AND \"ParentID\" = '$SQL_newsletterID'"
. " AND \"Email\" = '$SQL_email'");

// Update the Newsletter_SentRecipient record if it exists
if($oldNewsletterSentRecipient) {
$oldNewsletterSentRecipient->Result = 'Bounced';
$oldNewsletterSentRecipient->write();
} else {
// For some reason it didn't exist, create a new record
$newNewsletterSentRecipient = new Newsletter_SentRecipient();
$newNewsletterSentRecipient->Email = $SQL_email;
$newNewsletterSentRecipient->MemberID = $member->ID;
$newNewsletterSentRecipient->Result = 'Bounced';
$newNewsletterSentRecipient->ParentID = $newsletter_id_date_parts[0];
$newNewsletterSentRecipient->write();
}

// Now we are going to Blacklist this member so that email will not be sent to them in the future.
// Note: Sending can be re-enabled by going to 'Mailing List' 'Bounced' tab and unchecking the box
// under 'Blacklisted'
$member->setBlacklistedEmail(TRUE);
echo '<p><b>Member: '.$member->FirstName.' '.$member->Surname
.' <'.$member->Email.'> was added to the Email Blacklist!</b></p>';
}
}

if( !$date )
$date = date( 'd-m-Y' );
/*else
$date = date( 'd-m-Y', strtotime( $date ) );*/

if( !$time )
$time = date( 'H:i:s' );
/*else
$time = date( 'H:i:s', strtotime( $time ) );*/

$record->BounceEmail = $email;
$record->BounceTime = $date . ' ' . $time;
$record->BounceMessage = $error;
$record->write();

echo "Handled bounced email to address: $email";
} else {
echo 'Sorry, this bounce report has already been logged, not logging this duplicate bounce.';
}
}

}

/**
* Database record for recording a bounced email
* @package framework
* @subpackage email
*/
class Email_BounceRecord extends DataObject {
static $db = array(
'BounceEmail' => 'Varchar',
'BounceTime' => 'SS_Datetime',
'BounceMessage' => 'Varchar'
);

static $has_one = array(
'Member' => 'Member'
);

static $has_many = array();

static $many_many = array();

static $defaults = array();

static $singular_name = 'Email Bounce Record';


/**
* a record of Email_BounceRecord can't be created manually. Instead, it should be
* created though system.
*/
public function canCreate($member = null) {
return false;
}
}


3 changes: 0 additions & 3 deletions security/Member.php
Expand Up @@ -15,7 +15,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
'RememberLoginToken' => 'Varchar(160)', // Note: this currently holds a hash, not a token.
'NumVisit' => 'Int',
'LastVisited' => 'SS_Datetime',
'Bounced' => 'Boolean', // Note: This does not seem to be used anywhere.
'AutoLoginHash' => 'Varchar(160)',
'AutoLoginExpired' => 'SS_Datetime',
// This is an arbitrary code pointing to a PasswordEncryptor instance,
Expand Down Expand Up @@ -584,7 +583,6 @@ public function getMemberFormFields() {
$fields->removeByName('RememberLoginToken');
$fields->removeByName('NumVisit');
$fields->removeByName('LastVisited');
$fields->removeByName('Bounced');
$fields->removeByName('AutoLoginHash');
$fields->removeByName('AutoLoginExpired');
$fields->removeByName('PasswordEncryption');
Expand Down Expand Up @@ -1197,7 +1195,6 @@ public function getCMSFields() {
i18n::get_existing_translations()
));

$mainFields->removeByName('Bounced');
$mainFields->removeByName('RememberLoginToken');
$mainFields->removeByName('AutoLoginHash');
$mainFields->removeByName('AutoLoginExpired');
Expand Down

0 comments on commit 5fed5b9

Please sign in to comment.