2012-04-27 15:14:24 +00:00
|
|
|
<?php
|
2013-06-12 23:18:26 +00:00
|
|
|
/**
|
2014-08-05 00:15:14 +00:00
|
|
|
* This class represents the controller for notifications
|
2013-06-12 23:18:26 +00:00
|
|
|
*/
|
2012-04-27 15:14:24 +00:00
|
|
|
class EchoNotificationController {
|
2014-08-05 00:15:14 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Echo event agent per wiki blacklist
|
|
|
|
*
|
|
|
|
* @var string[]
|
|
|
|
*/
|
2013-05-06 22:34:50 +00:00
|
|
|
static protected $blacklist;
|
2014-08-05 00:15:14 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Echo event agent per user whitelist, this overwrites $blacklist
|
|
|
|
*
|
|
|
|
* @param string[]
|
|
|
|
*/
|
2013-05-06 22:34:50 +00:00
|
|
|
static protected $userWhitelist;
|
|
|
|
|
2012-11-13 23:06:11 +00:00
|
|
|
/**
|
|
|
|
* Format the notification count with Language::formatNum(). In addition, for large count,
|
|
|
|
* return abbreviated version, e.g. 99+
|
2014-08-05 00:15:14 +00:00
|
|
|
*
|
|
|
|
* @param int count
|
|
|
|
* @return string
|
2012-11-13 23:06:11 +00:00
|
|
|
*/
|
|
|
|
public static function formatNotificationCount( $count ) {
|
|
|
|
global $wgLang, $wgEchoMaxNotificationCount;
|
|
|
|
|
|
|
|
if ( $count > $wgEchoMaxNotificationCount ) {
|
|
|
|
$count = wfMessage(
|
|
|
|
'echo-notification-count',
|
|
|
|
$wgLang->formatNum( $wgEchoMaxNotificationCount )
|
|
|
|
)->escaped();
|
|
|
|
} else {
|
|
|
|
$count = $wgLang->formatNum( $count );
|
|
|
|
}
|
|
|
|
|
|
|
|
return $count;
|
|
|
|
}
|
|
|
|
|
2012-04-27 15:14:24 +00:00
|
|
|
/**
|
|
|
|
* Processes notifications for a newly-created EchoEvent
|
2012-08-31 21:50:46 +00:00
|
|
|
*
|
2014-08-05 00:15:14 +00:00
|
|
|
* @param EchoEvent $event
|
|
|
|
* @param boolean $defer Defer to job queue or not
|
2012-04-27 15:14:24 +00:00
|
|
|
*/
|
2012-06-06 07:04:28 +00:00
|
|
|
public static function notify( $event, $defer = true ) {
|
|
|
|
if ( $defer ) {
|
2013-08-30 02:05:29 +00:00
|
|
|
// defer job insertion till end of request when all primary db transactions
|
|
|
|
// have been committed
|
|
|
|
DeferredUpdates::addCallableUpdate(
|
|
|
|
function() use ( $event ) {
|
|
|
|
global $wgEchoCluster;
|
|
|
|
$params = array( 'event' => $event );
|
|
|
|
if ( wfGetLB()->getServerCount() > 1 ) {
|
|
|
|
$params['mainDbMasterPos'] = wfGetLB()->getMasterPos();
|
|
|
|
}
|
|
|
|
if ( $wgEchoCluster ) {
|
|
|
|
$lb = wfGetLBFactory()->getExternalLB( $wgEchoCluster );
|
|
|
|
if ( $lb->getServerCount() > 1 ) {
|
|
|
|
$params['echoDbMasterPos'] = $lb->getMasterPos();
|
|
|
|
}
|
|
|
|
}
|
2012-06-06 07:04:28 +00:00
|
|
|
|
2013-08-30 02:05:29 +00:00
|
|
|
$title = $event->getTitle() ? $event->getTitle() : Title::newMainPage();
|
|
|
|
$job = new EchoNotificationJob( $title, $params );
|
2013-10-07 21:23:19 +00:00
|
|
|
JobQueueGroup::singleton()->push( $job );
|
2013-08-30 02:05:29 +00:00
|
|
|
}
|
|
|
|
);
|
2012-06-06 07:04:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-03-01 01:09:40 +00:00
|
|
|
// Check if the event object has valid event type. Events with invalid
|
|
|
|
// event types left in the job queue should not be processed
|
2012-11-13 23:06:11 +00:00
|
|
|
if ( !$event->isEnabledEvent() ) {
|
2013-03-01 01:09:40 +00:00
|
|
|
return;
|
2012-11-13 23:06:11 +00:00
|
|
|
}
|
|
|
|
|
2013-01-15 23:21:39 +00:00
|
|
|
// Only send web notification for welcome event
|
|
|
|
if ( $event->getType() == 'welcome' ) {
|
2013-02-14 20:02:45 +00:00
|
|
|
self::doNotification( $event, $event->getAgent(), 'web' );
|
2012-08-31 23:35:16 +00:00
|
|
|
} else {
|
2013-01-15 23:21:39 +00:00
|
|
|
// Get the notification types for this event, eg, web/email
|
|
|
|
global $wgEchoDefaultNotificationTypes;
|
|
|
|
$notifyTypes = $wgEchoDefaultNotificationTypes['all'];
|
|
|
|
if ( isset( $wgEchoDefaultNotificationTypes[$event->getType()] ) ) {
|
|
|
|
$notifyTypes = array_merge( $notifyTypes, $wgEchoDefaultNotificationTypes[$event->getType()] );
|
|
|
|
}
|
|
|
|
$notifyTypes = array_keys( array_filter( $notifyTypes ) );
|
2012-04-27 15:14:24 +00:00
|
|
|
|
2013-01-15 23:21:39 +00:00
|
|
|
$users = self::getUsersToNotifyForEvent( $event );
|
2013-03-12 22:52:00 +00:00
|
|
|
|
2013-01-15 23:21:39 +00:00
|
|
|
foreach ( $users as $user ) {
|
2013-03-12 22:52:00 +00:00
|
|
|
|
2014-08-02 03:16:18 +00:00
|
|
|
$userNotifyTypes = $notifyTypes;
|
|
|
|
wfRunHooks( 'EchoGetNotificationTypes', array( $user, $event, &$userNotifyTypes ) );
|
2012-04-27 15:14:24 +00:00
|
|
|
|
2014-08-02 03:16:18 +00:00
|
|
|
foreach ( $userNotifyTypes as $type ) {
|
2012-08-31 23:35:16 +00:00
|
|
|
self::doNotification( $event, $user, $type );
|
|
|
|
}
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-06 22:34:50 +00:00
|
|
|
/**
|
|
|
|
* Implements blacklist per active wiki expected to be initialized
|
|
|
|
* from InitializeSettings.php
|
|
|
|
*
|
2014-08-05 00:15:14 +00:00
|
|
|
* @param EchoEvent $event The event to test for exclusion via global blacklist
|
2013-05-06 22:34:50 +00:00
|
|
|
* @return boolean True when the event agent is in the global blacklist
|
|
|
|
*/
|
|
|
|
protected static function isBlacklisted( EchoEvent $event ) {
|
|
|
|
if ( !$event->getAgent() ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( self::$blacklist === null ) {
|
|
|
|
global $wgEchoAgentBlacklist, $wgEchoOnWikiBlacklist,
|
|
|
|
$wgMemc;
|
|
|
|
|
|
|
|
self::$blacklist = new EchoContainmentSet;
|
|
|
|
self::$blacklist->addArray( $wgEchoAgentBlacklist );
|
|
|
|
if ( $wgEchoOnWikiBlacklist !== null ) {
|
|
|
|
self::$blacklist->addOnWiki(
|
|
|
|
NS_MEDIAWIKI,
|
|
|
|
$wgEchoOnWikiBlacklist,
|
|
|
|
$wgMemc,
|
|
|
|
wfMemcKey( "echo_on_wiki_blacklist")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return self::$blacklist->contains( $event->getAgent()->getName() );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements per-user whitelist sourced from a user wiki page
|
|
|
|
*
|
2014-08-05 00:15:14 +00:00
|
|
|
* @param EchoEvent $event The event to test for inclusion in whitelist
|
|
|
|
* @param User $user The user that owns the whitelist
|
2013-05-06 22:34:50 +00:00
|
|
|
* @return boolean True when the event agent is in the user whitelist
|
|
|
|
*/
|
|
|
|
protected static function isWhitelistedByUser( EchoEvent $event, User $user ) {
|
|
|
|
global $wgEchoPerUserWhitelistFormat, $wgMemc;
|
|
|
|
|
|
|
|
|
|
|
|
if ( $wgEchoPerUserWhitelistFormat === null || !$event->getAgent() ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$userId = $user->getID();
|
|
|
|
if ( $userId === 0 ) {
|
|
|
|
return false; // anonymous user
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !isset( self::$userWhitelist[$userId] ) ) {
|
|
|
|
self::$userWhitelist[$userId] = new EchoContainmentSet;
|
|
|
|
self::$userWhitelist[$userId]->addOnWiki(
|
|
|
|
NS_USER,
|
|
|
|
sprintf( $wgEchoPerUserWhitelistFormat, $user->getName() ),
|
|
|
|
$wgMemc,
|
|
|
|
wfMemcKey( "echo_on_wiki_whitelist_" . $userId )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return self::$userWhitelist[$userId]
|
|
|
|
->contains( $event->getAgent()->getName() );
|
|
|
|
}
|
|
|
|
|
2012-04-27 15:14:24 +00:00
|
|
|
/**
|
|
|
|
* Processes a single notification for an EchoEvent
|
|
|
|
*
|
2014-08-05 00:15:14 +00:00
|
|
|
* @param EchoEvent $event
|
|
|
|
* @param User $user The user to be notified.
|
|
|
|
* @param string $type The type of notification delivery to process, e.g. 'email'.
|
2012-05-17 00:29:37 +00:00
|
|
|
* @throws MWException
|
2012-04-27 15:14:24 +00:00
|
|
|
*/
|
|
|
|
public static function doNotification( $event, $user, $type ) {
|
|
|
|
global $wgEchoNotifiers;
|
|
|
|
|
2012-08-31 21:50:46 +00:00
|
|
|
if ( !isset( $wgEchoNotifiers[$type] ) ) {
|
2012-04-27 15:14:24 +00:00
|
|
|
throw new MWException( "Invalid notification type $type" );
|
|
|
|
}
|
|
|
|
|
2013-06-08 01:05:35 +00:00
|
|
|
// Don't send any notification if Echo is disabled
|
|
|
|
if ( EchoHooks::isEchoDisabled( $user ) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-04-27 15:14:24 +00:00
|
|
|
call_user_func_array( $wgEchoNotifiers[$type], array( $user, $event ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-30 03:18:48 +00:00
|
|
|
* Returns an array each element of which is the result of a
|
|
|
|
* user-locator attached to the event type.
|
2012-08-31 21:50:46 +00:00
|
|
|
*
|
2014-07-29 23:54:00 +00:00
|
|
|
* @param EchoEvent $event
|
2014-07-30 03:18:48 +00:00
|
|
|
* @return array
|
2012-04-27 15:14:24 +00:00
|
|
|
*/
|
2014-07-30 03:18:48 +00:00
|
|
|
public static function evaluateUserLocators( EchoEvent $event ) {
|
2014-07-29 23:54:00 +00:00
|
|
|
$attributeManager = EchoAttributeManager::newFromGlobalVars();
|
2014-07-30 03:18:48 +00:00
|
|
|
$type = $event->getType();
|
|
|
|
$result = array();
|
2014-07-29 23:54:00 +00:00
|
|
|
foreach ( $attributeManager->getUserLocators( $type ) as $callable ) {
|
2014-07-31 02:31:47 +00:00
|
|
|
// locator options can be set per-event by using an array with
|
2014-07-30 03:18:48 +00:00
|
|
|
// name as first parameter.
|
2014-07-31 02:31:47 +00:00
|
|
|
if ( is_array( $callable ) ) {
|
|
|
|
$options = $callable;
|
2014-07-30 03:18:48 +00:00
|
|
|
$spliced = array_splice( $options, 0, 1, array( $event ) );
|
|
|
|
$callable = reset( $spliced );
|
2014-07-31 02:31:47 +00:00
|
|
|
} else {
|
2014-07-30 03:18:48 +00:00
|
|
|
$options = array( $event );
|
2014-07-31 02:31:47 +00:00
|
|
|
}
|
2014-07-29 23:54:00 +00:00
|
|
|
if ( is_callable( $callable ) ) {
|
2014-07-30 03:18:48 +00:00
|
|
|
$result[] = call_user_func_array( $callable, $options );
|
2014-07-29 23:54:00 +00:00
|
|
|
} else {
|
|
|
|
wfDebugLog( __CLASS__, __FUNCTION__ . ": Invalid user-locator returned for $type" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-30 03:18:48 +00:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves an array of User objects to be notified for an EchoEvent.
|
|
|
|
*
|
|
|
|
* @param EchoEvent $event
|
|
|
|
* @return Iterator values are User objects
|
|
|
|
*/
|
|
|
|
public static function getUsersToNotifyForEvent( EchoEvent $event ) {
|
|
|
|
$notify = new EchoFilteredSequentialIterator;
|
|
|
|
foreach ( self::evaluateUserLocators( $event ) as $users ) {
|
|
|
|
$notify->add( $users );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hook for injecting more users.
|
2014-07-29 23:54:00 +00:00
|
|
|
// @deprecated
|
|
|
|
$users = array();
|
2012-04-27 15:14:24 +00:00
|
|
|
wfRunHooks( 'EchoGetDefaultNotifiedUsers', array( $event, &$users ) );
|
2014-07-30 03:18:48 +00:00
|
|
|
if ( $users ) {
|
|
|
|
$notify->add( $users );
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
2014-07-30 03:18:48 +00:00
|
|
|
// Filter non-User, anon and duplicate users
|
|
|
|
$seen = array();
|
|
|
|
$notify->addFilter( function( $user ) use( &$seen ) {
|
|
|
|
if ( !$user instanceof User ) {
|
|
|
|
wfDebugLog( __METHOD__, 'Expected all User instances, received:' .
|
|
|
|
( is_object( $user ) ? get_class( $user ) : gettype( $user ) )
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ( $user->isAnon() || isset( $seen[$user->getId()] ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$seen[$user->getId()] = true;
|
|
|
|
return true;
|
|
|
|
} );
|
|
|
|
|
|
|
|
// Don't notify the person who initiated the event unless the event extra says to do so
|
2013-01-26 12:14:36 +00:00
|
|
|
$extra = $event->getExtra();
|
|
|
|
if ( ( !isset( $extra['notifyAgent'] ) || !$extra['notifyAgent'] ) && $event->getAgent() ) {
|
2014-07-30 03:18:48 +00:00
|
|
|
$agentId = $event->getAgent()->getId();
|
|
|
|
$notify->addFilter( function( $user ) use( $agentId ) {
|
|
|
|
return $user->getId() != $agentId;
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply per-wiki event blacklist and per-user whitelists
|
|
|
|
// of that blacklist.
|
|
|
|
if ( self::isBlacklisted( $event ) ) {
|
|
|
|
$notify->addFilter( function( $user ) use( $event ) {
|
|
|
|
// don't use self:: - PHP5.3 closures don't inherit class scope
|
|
|
|
return EchoNotificationController::isWhitelistedByUser( $event, $user );
|
|
|
|
} );
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
2014-07-30 03:18:48 +00:00
|
|
|
return $notify->getIterator();
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Formats a notification
|
|
|
|
*
|
2014-08-05 00:15:14 +00:00
|
|
|
* @param EchoEvent $event The event for a notification.
|
|
|
|
* @param User $user The user to format the notification for.
|
|
|
|
* @param string $format The format to show the notification in: text, html, or email
|
|
|
|
* @param string $type The type of notification being distributed (e.g. email, web)
|
2012-11-26 22:57:28 +00:00
|
|
|
* @return string|array The formatted notification, or an array of subject
|
|
|
|
* and body (for emails), or an error message
|
2012-04-27 15:14:24 +00:00
|
|
|
*/
|
2013-02-14 20:02:45 +00:00
|
|
|
public static function formatNotification( $event, $user, $format = 'text', $type = 'web' ) {
|
2013-02-16 02:20:34 +00:00
|
|
|
global $wgEchoNotifications;
|
2012-04-27 15:14:24 +00:00
|
|
|
|
|
|
|
$eventType = $event->getType();
|
|
|
|
|
2014-02-06 23:06:54 +00:00
|
|
|
$res = '';
|
2013-02-16 02:20:34 +00:00
|
|
|
if ( isset( $wgEchoNotifications[$eventType] ) ) {
|
2014-02-06 23:06:54 +00:00
|
|
|
set_error_handler( array( __CLASS__, 'formatterErrorHandler' ), -1 );
|
2014-02-05 18:23:54 +00:00
|
|
|
try {
|
|
|
|
$params = $wgEchoNotifications[$eventType];
|
|
|
|
$notifier = EchoNotificationFormatter::factory( $params );
|
|
|
|
$notifier->setOutputFormat( $format );
|
|
|
|
|
2014-02-06 23:06:54 +00:00
|
|
|
$res = $notifier->format( $event, $user, $type );
|
2014-02-05 18:23:54 +00:00
|
|
|
} catch ( Exception $e ) {
|
|
|
|
$meta = array(
|
|
|
|
'id' => $event->getId(),
|
|
|
|
'eventType' => $eventType,
|
|
|
|
'format' => $format,
|
|
|
|
'type' => $type,
|
|
|
|
'user' => $user ? $user->getName() : 'no user',
|
|
|
|
);
|
|
|
|
wfDebugLog( __CLASS__, __FUNCTION__ . ": Error formatting " . FormatJson::encode( $meta ) );
|
|
|
|
MWExceptionHandler::logException( $e );
|
|
|
|
}
|
2014-02-06 23:06:54 +00:00
|
|
|
restore_error_handler();
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 23:06:54 +00:00
|
|
|
if ( $res ) {
|
|
|
|
return $res;
|
|
|
|
} else {
|
|
|
|
return Xml::tags( 'span', array( 'class' => 'error' ),
|
|
|
|
wfMessage( 'echo-error-no-formatter', $event->getType() )->escaped() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* INTERNAL. Must be public to be callable by the php error handling methods.
|
|
|
|
*
|
2014-07-29 23:54:00 +00:00
|
|
|
* Converts E_RECOVERABLE_ERROR, such as passing null to a method expecting
|
2014-02-06 23:06:54 +00:00
|
|
|
* a non-null object, into exceptions.
|
|
|
|
*/
|
|
|
|
public static function formatterErrorHandler( $errno, $errstr, $errfile, $errline ) {
|
|
|
|
if ( $errno !== E_RECOVERABLE_ERROR ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-08-05 00:15:14 +00:00
|
|
|
throw new EchoCatchableFatalErrorException( $errno, $errstr, $errfile, $errline );
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
2012-06-08 05:33:25 +00:00
|
|
|
}
|