From 5fed5b91c996514d44297db45b58474e379c9b7b Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 3 Dec 2012 01:03:18 +0100 Subject: [PATCH] API Moved email bounce handling to new 'emailbouncehandler' module --- _config.php | 14 +-- docs/en/changelogs/3.1.0.md | 6 +- email/Email.php | 193 ++---------------------------------- security/Member.php | 3 - 4 files changed, 13 insertions(+), 203 deletions(-) diff --git a/_config.php b/_config.php index fea04185e..243d2f9bc 100644 --- a/_config.php +++ b/_config.php @@ -34,18 +34,6 @@ define('MCE_ROOT', FRAMEWORK_DIR . '/thirdparty/tinymce/'); 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 @@ -60,4 +48,4 @@ SS_Cache::pick_backend('aggregatestore', 'aggregate', 1000); 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'); \ No newline at end of file diff --git a/docs/en/changelogs/3.1.0.md b/docs/en/changelogs/3.1.0.md index 08a883616..78a6149c4 100644 --- a/docs/en/changelogs/3.1.0.md +++ b/docs/en/changelogs/3.1.0.md @@ -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)). \ No newline at end of file diff --git a/email/Email.php b/email/Email.php index 337849071..376617545 100644 --- a/email/Email.php +++ b/email/Email.php @@ -16,8 +16,6 @@ if(isset($_SERVER['SERVER_NAME'])) { */ 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. @@ -113,11 +111,6 @@ class Email extends ViewableData { */ 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 @@ -151,7 +144,11 @@ class Email extends ViewableData { 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(); } @@ -163,12 +160,8 @@ class Email extends ViewableData { ); } - 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) { @@ -388,12 +381,8 @@ class Email extends ViewableData { 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(); @@ -449,12 +438,8 @@ class Email extends ViewableData { 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(); @@ -620,167 +605,3 @@ class Email extends ViewableData { } } } - -/** - * 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 '

Member: '.$member->FirstName.' '.$member->Surname - .' <'.$member->Email.'> was added to the Email Blacklist!

'; - } - } - - 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; - } -} - - diff --git a/security/Member.php b/security/Member.php index 137a7e2de..5313b9781 100644 --- a/security/Member.php +++ b/security/Member.php @@ -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, @@ -584,7 +583,6 @@ class Member extends DataObject implements TemplateGlobalProvider { $fields->removeByName('RememberLoginToken'); $fields->removeByName('NumVisit'); $fields->removeByName('LastVisited'); - $fields->removeByName('Bounced'); $fields->removeByName('AutoLoginHash'); $fields->removeByName('AutoLoginExpired'); $fields->removeByName('PasswordEncryption'); @@ -1197,7 +1195,6 @@ class Member extends DataObject implements TemplateGlobalProvider { i18n::get_existing_translations() )); - $mainFields->removeByName('Bounced'); $mainFields->removeByName('RememberLoginToken'); $mainFields->removeByName('AutoLoginHash'); $mainFields->removeByName('AutoLoginExpired');