mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-24 07:54:13 +00:00
Get rid of email bundling
Bug: T135446 Change-Id: I95dc3d70c82c19d89b7a32be0c36294350d5c30d
This commit is contained in:
parent
46e69972c6
commit
bb7a15e8be
|
@ -108,11 +108,9 @@ $wgAutoloadClasses += [
|
||||||
'MWEchoDbFactory' => __DIR__ . '/includes/EchoDbFactory.php',
|
'MWEchoDbFactory' => __DIR__ . '/includes/EchoDbFactory.php',
|
||||||
'MWEchoDbFactoryTest' => __DIR__ . '/tests/phpunit/EchoDbFactoryTest.php',
|
'MWEchoDbFactoryTest' => __DIR__ . '/tests/phpunit/EchoDbFactoryTest.php',
|
||||||
'MWEchoEmailBatch' => __DIR__ . '/includes/EmailBatch.php',
|
'MWEchoEmailBatch' => __DIR__ . '/includes/EmailBatch.php',
|
||||||
'MWEchoEmailBundler' => __DIR__ . '/includes/EmailBundler.php',
|
|
||||||
'MWEchoEventLogging' => __DIR__ . '/includes/EventLogging.php',
|
'MWEchoEventLogging' => __DIR__ . '/includes/EventLogging.php',
|
||||||
'MWEchoNotifUser' => __DIR__ . '/includes/NotifUser.php',
|
'MWEchoNotifUser' => __DIR__ . '/includes/NotifUser.php',
|
||||||
'MWEchoNotifUserTest' => __DIR__ . '/tests/phpunit/NotifUserTest.php',
|
'MWEchoNotifUserTest' => __DIR__ . '/tests/phpunit/NotifUserTest.php',
|
||||||
'MWEchoNotificationEmailBundleJob' => __DIR__ . '/includes/jobs/NotificationEmailBundleJob.php',
|
|
||||||
'NotificationControllerTest' => __DIR__ . '/tests/phpunit/controller/NotificationControllerTest.php',
|
'NotificationControllerTest' => __DIR__ . '/tests/phpunit/controller/NotificationControllerTest.php',
|
||||||
'NotificationsTest' => __DIR__ . '/tests/NotificationsTest.php',
|
'NotificationsTest' => __DIR__ . '/tests/NotificationsTest.php',
|
||||||
'SpecialDisplayNotificationsConfiguration' => __DIR__ . '/includes/special/SpecialDisplayNotificationsConfiguration.php',
|
'SpecialDisplayNotificationsConfiguration' => __DIR__ . '/includes/special/SpecialDisplayNotificationsConfiguration.php',
|
||||||
|
|
|
@ -1,310 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class handles email bundling, it has only two public interfacing entries:
|
|
||||||
*
|
|
||||||
* 1. a single notification is triggered which calls self::addToEmailBatch()
|
|
||||||
* (a) cycle is null/reset, send single notification, schedule a bundle job for next notification
|
|
||||||
* (b) cycle is in bundle mode, add the notification to the queue
|
|
||||||
*
|
|
||||||
* 2. a job is popped off the queue which calls self::processBundleEmail()
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class MWEchoEmailBundler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var User
|
|
||||||
*/
|
|
||||||
protected $mUser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $bundleHash;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* The timestamp of email being sent
|
|
||||||
*/
|
|
||||||
protected $timestamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var EchoEvent
|
|
||||||
*/
|
|
||||||
protected $baseEvent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*
|
|
||||||
* seconds between sending batch email for a bundle notification
|
|
||||||
* this only applies to a bundle type
|
|
||||||
*/
|
|
||||||
protected $emailInterval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Protected constructor so subclasses can call it
|
|
||||||
*/
|
|
||||||
protected function __construct( $user, $hash ) {
|
|
||||||
global $wgEchoBundleEmailInterval;
|
|
||||||
|
|
||||||
$this->mUser = $user;
|
|
||||||
$this->bundleHash = $hash;
|
|
||||||
$this->emailInterval = $wgEchoBundleEmailInterval;
|
|
||||||
|
|
||||||
if ( $this->emailInterval < 0 ) {
|
|
||||||
$this->emailInterval = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory method
|
|
||||||
*/
|
|
||||||
public static function newFromUserHash( User $user, $hash ) {
|
|
||||||
if ( !$user->getId() ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ( !$hash || !preg_match( '/^[a-f0-9]{32}$/', $hash ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new self( $user, $hash );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a new notification should be added to the batch queue
|
|
||||||
* true - added to the queue for bundling email
|
|
||||||
* false - not added, the client should send single email
|
|
||||||
*
|
|
||||||
* @param int $eventId
|
|
||||||
* @param int $eventPriority
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function addToEmailBatch( $eventId, $eventPriority ) {
|
|
||||||
$this->retrieveLastEmailTimestamp();
|
|
||||||
$this->retrieveBaseEvent();
|
|
||||||
|
|
||||||
// send instant single notification email if there is no base event in the batch queue
|
|
||||||
// and the email is ready to send, otherwiase, add the email to batch and schedule
|
|
||||||
// a delayed job
|
|
||||||
if ( !$this->baseEvent && $this->shouldSendEmailNow() ) {
|
|
||||||
$this->timestamp = wfTimestampNow();
|
|
||||||
$this->updateEmailMetadata();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// add to email batch queue
|
|
||||||
MWEchoEmailBatch::addToQueue(
|
|
||||||
$this->mUser->getId(),
|
|
||||||
$eventId,
|
|
||||||
$eventPriority,
|
|
||||||
$this->bundleHash
|
|
||||||
);
|
|
||||||
|
|
||||||
// always push the job to job queue in case the previous job
|
|
||||||
// was lost, job queue will ignore duplicate
|
|
||||||
$this->pushToJobQueue( $this->getDelayTime() );
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the time diff since last email
|
|
||||||
*/
|
|
||||||
protected function timeSinceLastEmail() {
|
|
||||||
// if there is no timestamp, next email should be sent right away
|
|
||||||
// set the time diff longer than the email interval
|
|
||||||
if ( !$this->timestamp ) {
|
|
||||||
return $this->emailInterval + 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
$now = wfTimestamp( TS_UNIX );
|
|
||||||
|
|
||||||
return $now - wfTimestamp( TS_UNIX, $this->timestamp );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an email should be sent right away
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
protected function shouldSendEmailNow() {
|
|
||||||
if ( $this->timeSinceLastEmail() > $this->emailInterval ) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the delay time
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
protected function getDelayTime() {
|
|
||||||
$delay = $this->emailInterval - $this->timeSinceLastEmail();
|
|
||||||
if ( $delay <= 0 ) {
|
|
||||||
$delay = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the timestamp of last email
|
|
||||||
*/
|
|
||||||
protected function retrieveLastEmailTimestamp() {
|
|
||||||
$data = ObjectCache::getMainStashInstance()->get( $this->getMemcacheKey() );
|
|
||||||
if ( $data !== false ) {
|
|
||||||
$this->timestamp = $data['timestamp'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the memcache key
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function getMemcacheKey() {
|
|
||||||
return wfMemcKey( 'echo', 'email_bundle_status', $this->mUser->getId(), $this->bundleHash );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the base event for email bundling, the one with the largest eeb_id
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
protected function retrieveBaseEvent() {
|
|
||||||
$dbr = MWEchoDbFactory::getDB( DB_SLAVE );
|
|
||||||
$res = $dbr->selectRow(
|
|
||||||
array( 'echo_email_batch' ),
|
|
||||||
array( 'eeb_event_id' ),
|
|
||||||
array(
|
|
||||||
'eeb_user_id' => $this->mUser->getId(),
|
|
||||||
'eeb_event_hash' => $this->bundleHash
|
|
||||||
),
|
|
||||||
__METHOD__,
|
|
||||||
array( 'ORDER BY' => 'eeb_event_priority DESC, eeb_id DESC', 'LIMIT' => 1 )
|
|
||||||
);
|
|
||||||
if ( !$res ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$this->baseEvent = EchoEvent::newFromId( $res->eeb_event_id );
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push the latest bundle data to the queue
|
|
||||||
* @param $delay int To delay the job in $delay seconds
|
|
||||||
*/
|
|
||||||
public function pushToJobQueue( $delay = 0 ) {
|
|
||||||
$title = Title::newMainPage();
|
|
||||||
$job = new MWEchoNotificationEmailBundleJob(
|
|
||||||
$title,
|
|
||||||
array(
|
|
||||||
'user_id' => $this->mUser->getId(),
|
|
||||||
'bundle_hash' => $this->bundleHash,
|
|
||||||
'jobReleaseTimestamp' => wfTimestamp( TS_MW, wfTimestamp( TS_UNIX ) + $delay )
|
|
||||||
)
|
|
||||||
);
|
|
||||||
JobQueueGroup::singleton()->push( $job );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main function for processinig bundle email
|
|
||||||
*/
|
|
||||||
public function processBundleEmail() {
|
|
||||||
$emailSetting = intval( $this->mUser->getOption( 'echo-email-frequency' ) );
|
|
||||||
|
|
||||||
// User has switched to email digest or decided not to receive email,
|
|
||||||
// the daily cron will handle events left in the queue
|
|
||||||
if ( $emailSetting != 0 ) {
|
|
||||||
throw new MWException( "User has switched to email digest/no email option!" );
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is nothing in the queue, do not update timestamp so next
|
|
||||||
// email would be just an instant email
|
|
||||||
if ( $this->retrieveBaseEvent() ) {
|
|
||||||
$this->timestamp = wfTimestampNow();
|
|
||||||
$this->updateEmailMetadata();
|
|
||||||
$this->sendEmail();
|
|
||||||
$this->clearProcessedEvent();
|
|
||||||
} else {
|
|
||||||
throw new MWException( "There is no bundle notification to process!" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the bundle email
|
|
||||||
*/
|
|
||||||
protected function sendEmail() {
|
|
||||||
$content = $this->generateEmailContent();
|
|
||||||
|
|
||||||
if ( !isset( $content['subject'] ) || !isset( $content['body'] ) ) {
|
|
||||||
throw new MWException( "Fail to create bundle email content!" );
|
|
||||||
}
|
|
||||||
|
|
||||||
global $wgNotificationSender, $wgNotificationReplyName;
|
|
||||||
|
|
||||||
$toAddress = MailAddress::newFromUser( $this->mUser );
|
|
||||||
$fromAddress = new MailAddress( $wgNotificationSender, EchoHooks::getNotificationSenderName() );
|
|
||||||
$replyAddress = new MailAddress( $wgNotificationSender, $wgNotificationReplyName );
|
|
||||||
|
|
||||||
// Schedule a email job or just send the email directly?
|
|
||||||
UserMailer::send(
|
|
||||||
$toAddress,
|
|
||||||
$fromAddress,
|
|
||||||
$content['subject'],
|
|
||||||
$content['body'],
|
|
||||||
array( 'replyTo' => $replyAddress )
|
|
||||||
);
|
|
||||||
MWEchoEventLogging::logSchemaEchoMail( $this->mUser, 'bundle' );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the content for bundle email
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function generateEmailContent() {
|
|
||||||
if ( !$this->baseEvent ) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
$this->baseEvent->setBundleHash( $this->bundleHash );
|
|
||||||
|
|
||||||
return EchoNotificationController::formatNotification( $this->baseEvent, $this->mUser, 'email', 'email' );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update bundle email metadata for user/hash pair
|
|
||||||
*/
|
|
||||||
protected function updateEmailMetadata() {
|
|
||||||
$key = $this->getMemcacheKey();
|
|
||||||
|
|
||||||
// Store new data and make it expire in 7 days
|
|
||||||
ObjectCache::getMainStashInstance()->set(
|
|
||||||
$key,
|
|
||||||
array(
|
|
||||||
'timestamp' => $this->timestamp
|
|
||||||
),
|
|
||||||
3600 * 24 * 7
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* clear processed event in the queue
|
|
||||||
*/
|
|
||||||
protected function clearProcessedEvent() {
|
|
||||||
if ( !$this->baseEvent ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$conds = array( 'eeb_user_id' => $this->mUser->getId(), 'eeb_event_hash' => $this->bundleHash );
|
|
||||||
|
|
||||||
$conds[] = 'eeb_event_id <= ' . intval( $this->baseEvent->getId() );
|
|
||||||
|
|
||||||
$dbw = MWEchoDbFactory::newFromDefault()->getEchoDb( DB_MASTER );
|
|
||||||
$dbw->delete(
|
|
||||||
'echo_email_batch',
|
|
||||||
$conds,
|
|
||||||
__METHOD__
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -50,7 +50,7 @@ class EchoNotifier {
|
||||||
$userEmailNotifications = $attributeManager->getUserEnabledEvents( $user, 'email' );
|
$userEmailNotifications = $attributeManager->getUserEnabledEvents( $user, 'email' );
|
||||||
// See if the user wants to receive emails for this category or the user is eligible to receive this email
|
// See if the user wants to receive emails for this category or the user is eligible to receive this email
|
||||||
if ( in_array( $event->getType(), $userEmailNotifications ) ) {
|
if ( in_array( $event->getType(), $userEmailNotifications ) ) {
|
||||||
global $wgEchoEnableEmailBatch, $wgEchoNotifications, $wgNotificationSender, $wgNotificationReplyName, $wgEchoBundleEmailInterval;
|
global $wgEchoEnableEmailBatch, $wgEchoNotifications, $wgNotificationSender, $wgNotificationReplyName;
|
||||||
|
|
||||||
$priority = $attributeManager->getNotificationPriority( $event->getType() );
|
$priority = $attributeManager->getNotificationPriority( $event->getType() );
|
||||||
|
|
||||||
|
@ -79,36 +79,23 @@ class EchoNotifier {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$addedToQueue = false;
|
// instant email notification
|
||||||
|
$toAddress = MailAddress::newFromUser( $user );
|
||||||
// only send bundle email if email bundling is on
|
$fromAddress = new MailAddress( $wgNotificationSender, EchoHooks::getNotificationSenderName() );
|
||||||
if ( $wgEchoBundleEmailInterval && $bundleHash && !empty( $wgEchoNotifications[$event->getType()]['bundle']['email'] ) ) {
|
$replyAddress = new MailAddress( $wgNotificationSender, $wgNotificationReplyName );
|
||||||
$bundler = MWEchoEmailBundler::newFromUserHash( $user, $bundleHash );
|
// Since we are sending a single email, should set the bundle hash to null
|
||||||
if ( $bundler ) {
|
// if it is set with a value from somewhere else
|
||||||
$addedToQueue = $bundler->addToEmailBatch( $event->getId(), $priority );
|
$event->setBundleHash( null );
|
||||||
}
|
$email = self::generateEmail( $event, $user );
|
||||||
|
if ( !$email ) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
$subject = $email['subject'];
|
||||||
|
$body = $email['body'];
|
||||||
|
$options = array( 'replyTo' => $replyAddress );
|
||||||
|
|
||||||
// send single notification if the email wasn't added to queue for bundling
|
UserMailer::send( $toAddress, $fromAddress, $subject, $body, $options );
|
||||||
if ( !$addedToQueue ) {
|
MWEchoEventLogging::logSchemaEchoMail( $user, 'single' );
|
||||||
// instant email notification
|
|
||||||
$toAddress = MailAddress::newFromUser( $user );
|
|
||||||
$fromAddress = new MailAddress( $wgNotificationSender, EchoHooks::getNotificationSenderName() );
|
|
||||||
$replyAddress = new MailAddress( $wgNotificationSender, $wgNotificationReplyName );
|
|
||||||
// Since we are sending a single email, should set the bundle hash to null
|
|
||||||
// if it is set with a value from somewhere else
|
|
||||||
$event->setBundleHash( null );
|
|
||||||
$email = self::generateEmail( $event, $user );
|
|
||||||
if ( !$email ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$subject = $email['subject'];
|
|
||||||
$body = $email['body'];
|
|
||||||
$options = array( 'replyTo' => $replyAddress );
|
|
||||||
|
|
||||||
UserMailer::send( $toAddress, $fromAddress, $subject, $body, $options );
|
|
||||||
MWEchoEventLogging::logSchemaEchoMail( $user, 'single' );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
class MWEchoNotificationEmailBundleJob extends Job {
|
|
||||||
function __construct( $title, $params ) {
|
|
||||||
parent::__construct( __CLASS__, $title, $params );
|
|
||||||
// If there is already a job with the same params, this job will be ignored
|
|
||||||
// for example, if there is a page link bundle notification job for article A
|
|
||||||
// created by user B, any subsequent jobs with the same data will be ignored
|
|
||||||
$this->removeDuplicates = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function run() {
|
|
||||||
$bundle = MWEchoEmailBundler::newFromUserHash(
|
|
||||||
User::newFromId( $this->params['user_id'] ),
|
|
||||||
$this->params['bundle_hash']
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( $bundle ) {
|
|
||||||
$bundle->processBundleEmail();
|
|
||||||
} else {
|
|
||||||
throw new MWException( 'Fail to create bundle object for: user_id: ' . $this->params['user_id'] . ', bundle_hash: ' . $this->params['bundle_hash'] );
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue