mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-28 01:30:15 +00:00
Merge "Get rid of email bundling"
This commit is contained in:
commit
7d2d4f2c0a
|
@ -108,11 +108,9 @@ $wgAutoloadClasses += [
|
|||
'MWEchoDbFactory' => __DIR__ . '/includes/EchoDbFactory.php',
|
||||
'MWEchoDbFactoryTest' => __DIR__ . '/tests/phpunit/EchoDbFactoryTest.php',
|
||||
'MWEchoEmailBatch' => __DIR__ . '/includes/EmailBatch.php',
|
||||
'MWEchoEmailBundler' => __DIR__ . '/includes/EmailBundler.php',
|
||||
'MWEchoEventLogging' => __DIR__ . '/includes/EventLogging.php',
|
||||
'MWEchoNotifUser' => __DIR__ . '/includes/NotifUser.php',
|
||||
'MWEchoNotifUserTest' => __DIR__ . '/tests/phpunit/NotifUserTest.php',
|
||||
'MWEchoNotificationEmailBundleJob' => __DIR__ . '/includes/jobs/NotificationEmailBundleJob.php',
|
||||
'NotificationControllerTest' => __DIR__ . '/tests/phpunit/controller/NotificationControllerTest.php',
|
||||
'NotificationsTest' => __DIR__ . '/tests/NotificationsTest.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' );
|
||||
// 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 ) ) {
|
||||
global $wgEchoEnableEmailBatch, $wgEchoNotifications, $wgNotificationSender, $wgNotificationReplyName, $wgEchoBundleEmailInterval;
|
||||
global $wgEchoEnableEmailBatch, $wgEchoNotifications, $wgNotificationSender, $wgNotificationReplyName;
|
||||
|
||||
$priority = $attributeManager->getNotificationPriority( $event->getType() );
|
||||
|
||||
|
@ -79,36 +79,23 @@ class EchoNotifier {
|
|||
return true;
|
||||
}
|
||||
|
||||
$addedToQueue = false;
|
||||
|
||||
// only send bundle email if email bundling is on
|
||||
if ( $wgEchoBundleEmailInterval && $bundleHash && !empty( $wgEchoNotifications[$event->getType()]['bundle']['email'] ) ) {
|
||||
$bundler = MWEchoEmailBundler::newFromUserHash( $user, $bundleHash );
|
||||
if ( $bundler ) {
|
||||
$addedToQueue = $bundler->addToEmailBatch( $event->getId(), $priority );
|
||||
}
|
||||
// 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 );
|
||||
|
||||
// send single notification if the email wasn't added to queue for bundling
|
||||
if ( !$addedToQueue ) {
|
||||
// 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' );
|
||||
}
|
||||
UserMailer::send( $toAddress, $fromAddress, $subject, $body, $options );
|
||||
MWEchoEventLogging::logSchemaEchoMail( $user, 'single' );
|
||||
}
|
||||
|
||||
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