jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/320718 )
Change subject: Implement per-user notification blacklists ...................................................................... Implement per-user notification blacklists - Disabled by default, is a gated preference by wg variable - User specifies blocks in Echo preferences - Uses a TextArea with username separated by new lines as input - Still allows notifications to come through on a user's talk page - Cache the blacklist and whitelist Requested at <https://meta.wikimedia.org/wiki/2016_Community_Wishlist_Survey/Categories/Miscellaneous#Allow_users_to_restrict_who_can_send_them_notifications>. Bug: T150419 Change-Id: Ibf548da4aa600bdc7848cba1947436e56ac48a4a --- M Hooks.php M extension.json M i18n/en.json M i18n/qqq.json M includes/ContainmentSet.php M includes/controller/NotificationController.php M tests/phpunit/ContainmentSetTest.php 7 files changed, 144 insertions(+), 43 deletions(-) Approvals: Catrope: Looks good to me, approved jenkins-bot: Verified diff --git a/Hooks.php b/Hooks.php index c256093..f3cb933 100644 --- a/Hooks.php +++ b/Hooks.php @@ -307,7 +307,7 @@ global $wgEchoEnableEmailBatch, $wgEchoNotifiers, $wgEchoNotificationCategories, $wgEchoNotifications, $wgEchoNewMsgAlert, $wgAllowHTMLEmail, $wgEchoUseCrossWikiBetaFeature, - $wgEchoShowFooterNotice, $wgEchoCrossWikiNotifications; + $wgEchoShowFooterNotice, $wgEchoCrossWikiNotifications, $wgEchoPerUserBlacklist; $attributeManager = EchoAttributeManager::newFromGlobalVars(); @@ -483,6 +483,14 @@ ]; } + if ( $wgEchoPerUserBlacklist ) { + $preferences['echo-notifications-blacklist'] = [ + 'type' => 'textarea', + 'label-message' => 'echo-pref-notifications-blacklist', + 'section' => 'echo/blocknotificationslist', + ]; + } + return true; } diff --git a/extension.json b/extension.json index 2dec0f1..68ff130 100644 --- a/extension.json +++ b/extension.json @@ -664,6 +664,9 @@ "EchoOnWikiBlacklist": { "value": "Echo-blacklist" }, + "EchoPerUserBlacklist": { + "value": false + }, "EchoPerUserWhitelistFormat": { "value": "%s/Echo-whitelist" }, diff --git a/i18n/en.json b/i18n/en.json index d5cac50..1860864 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -42,6 +42,7 @@ "prefs-echosubscriptions": "Notify me about these events", "prefs-echocrosswiki": "Cross-wiki notifications", "prefs-newmessageindicator": "New message indicator", + "prefs-blocknotificationslist": "Block list", "echo-pref-send-me": "Send me:", "echo-pref-send-to": "Send to:", "echo-pref-email-format": "Email format:", @@ -54,6 +55,7 @@ "echo-pref-email-format-html": "HTML", "echo-pref-email-format-plain-text": "Plain text", "echo-pref-cross-wiki-notifications": "Show notifications from other wikis", + "echo-pref-notifications-blacklist": "List of usernames that are blacklisted from triggering most Echo notifications (edits to your user talk page will still trigger notifications)", "echo-pref-new-message-indicator": "Show talk page message indicator in my toolbar", "echo-pref-beta-feature-cross-wiki-message": "Enhanced notifications", "echo-pref-beta-feature-cross-wiki-description": "View and organize notifications more easily. Includes cross-wiki notifications, which lets you see messages from other wikis. (To receive cross-wiki notifications on a given wiki, you must activate the beta feature on that wiki.)", diff --git a/i18n/qqq.json b/i18n/qqq.json index f0afc55..baff397 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -36,6 +36,7 @@ "prefs-echosubscriptions": "Header for the section of preferences that deals with which notifications the user receives", "prefs-echocrosswiki": "Header for the section of preferences that deals with notifications from other wikis", "prefs-newmessageindicator": "Header for the section of preferences that deals with talk page message alerts", + "prefs-blocknotificationslist": "Header for the section of preferences that deals with blocking notifications from certain users", "echo-pref-send-me": "Label for the following email delivery options:\n* {{msg-mw|Echo-pref-email-frequency-never}}\n* {{msg-mw|Echo-pref-email-frequency-immediately}} (default)\n* {{msg-mw|Echo-pref-email-frequency-daily}}\n* {{msg-mw|Echo-pref-email-frequency-weekly}}", "echo-pref-send-to": "Label for the address to send email notifications to.", "echo-pref-email-format": "Label for individual email notification format, the label will be updated once HTML email is ready for email digest.\n\nUsed as label for the select box which has the following options:\n* {{msg-mw|Echo-pref-email-format-html}}\n* {{msg-mw|Echo-pref-email-format-plain-text}}", @@ -48,6 +49,7 @@ "echo-pref-email-format-html": "Option for users who want to receive HTML email notification.\n\nSee also:\n* {{msg-mw|Echo-pref-email-format}}\n{{Identical|HTML}}", "echo-pref-email-format-plain-text": "Option for users who want to receive plain text email notification.\n\nSee also:\n* {{msg-mw|Echo-pref-email-format}}\n{{Identical|Plain text}}", "echo-pref-cross-wiki-notifications": "Label for a preference which enables notifications from other wikis. Only used if {{msg-mw|echo-pref-beta-feature-cross-wiki-message}} is not used.", + "echo-pref-notifications-blacklist": "Label for a preference which allows a user to block notifications from certain users", "echo-pref-new-message-indicator": "Label for a preference which enables the new talk page message alert", "echo-pref-beta-feature-cross-wiki-message": "Label for the cross-wiki notifications Beta Feature. Only used if {{msg-mw|echo-pref-cross-wiki-notifications}} is not used.", "echo-pref-beta-feature-cross-wiki-description": "Description for the cross-wiki notifications Beta Feature, describing the feature that will be enabled. Only used if {{msg-mw|echo-pref-cross-wiki-notifications}} is not used.", diff --git a/includes/ContainmentSet.php b/includes/ContainmentSet.php index b5d14fc..6bde088 100644 --- a/includes/ContainmentSet.php +++ b/includes/ContainmentSet.php @@ -39,6 +39,15 @@ protected $lists = []; /** + * @var User + */ + protected $recipient; + + public function __construct( User $recipient ) { + $this->recipient = $recipient; + } + + /** * Add an EchoContainmentList to the set of lists checked by self::contains() * * @param $list EchoContainmentList @@ -57,6 +66,21 @@ } /** + * Add a list from a user preference to the set of lists checked by self::contains(). + * + * @param $preferenceName string + */ + public function addFromUserOption( $preferenceName ) { + $preference = $this->recipient->getOption( $preferenceName ); + + if ( $preference ) { + $items = explode( "\n", $preference ); + + $this->addArray( $items ); + } + } + + /** * Add a list from a wiki page to the set of lists checked by self::contains(). Data * from wiki pages is cached via the BagOStuff. Caching is disabled when passing a null * $cache object. diff --git a/includes/controller/NotificationController.php b/includes/controller/NotificationController.php index 7e7a3df..66ced23 100644 --- a/includes/controller/NotificationController.php +++ b/includes/controller/NotificationController.php @@ -6,18 +6,32 @@ class EchoNotificationController { /** - * Echo event agent per wiki blacklist + * Echo maximum number of users to cache * - * @var string[] + * @var int $maxRecipientCacheSize */ - static protected $blacklist; + static protected $maxRecipientCacheSize = 200; /** - * Echo event agent per user whitelist, this overwrites $blacklist + * Echo event agent per user blacklist * - * @param string[] + * @var MapCacheLRU */ - static protected $userWhitelist; + static protected $blacklistByUser; + + /** + * Echo event agent per wiki blacklist + * + * @var EchoContainmentList|null + */ + static protected $wikiBlacklist; + + /** + * Echo event agent per user whitelist, this overwrites $blacklistByUser + * + * @var MapCacheLRU + */ + static protected $whitelistByUser; /** * Returns the count passed in, or MWEchoNotifUser::MAX_BADGE_COUNT + 1, @@ -193,30 +207,69 @@ * Implements blacklist per active wiki expected to be initialized * from InitializeSettings.php * - * @param EchoEvent $event The event to test for exclusion via global blacklist - * @return boolean True when the event agent is in the global blacklist + * @param EchoEvent $event The event to test for exclusion + * @param User $user recipient of the notification for per-user blacklists + * @return boolean True when the event agent is blacklisted */ - protected static function isBlacklisted( EchoEvent $event ) { + public static function isBlacklistedByUser( EchoEvent $event, User $user ) { + global $wgEchoAgentBlacklist, $wgEchoPerUserBlacklist; + + $clusterCache = ObjectCache::getLocalClusterInstance(); + if ( !$event->getAgent() ) { return false; } - if ( self::$blacklist === null ) { - global $wgEchoAgentBlacklist, $wgEchoOnWikiBlacklist; - - self::$blacklist = new EchoContainmentSet; - self::$blacklist->addArray( $wgEchoAgentBlacklist ); - if ( $wgEchoOnWikiBlacklist !== null ) { - self::$blacklist->addOnWiki( - NS_MEDIAWIKI, - $wgEchoOnWikiBlacklist, - ObjectCache::getLocalClusterInstance(), - wfMemcKey( "echo_on_wiki_blacklist" ) - ); - } + // Ensure we have a list of blacklists + if ( self::$blacklistByUser === null ) { + self::$blacklistByUser = new MapCacheLRU( self::$maxRecipientCacheSize ); } - return self::$blacklist->contains( $event->getAgent()->getName() ); + // Ensure we have a blacklist for the user + if ( !self::$blacklistByUser->has( $user->getId() ) ) { + $blacklist = new EchoContainmentSet( $user ); + + // Add the config setting + $blacklist->addArray( $wgEchoAgentBlacklist ); + + // Add wiki-wide blacklist + $wikiBlacklist = self::getWikiBlacklist(); + if ( $wikiBlacklist !== null ) { + $blacklist->add( $wikiBlacklist ); + } + + // Add to blacklist from user preference + if ( $wgEchoPerUserBlacklist ) { + $blacklist->addFromUserOption( 'echo-notifications-blacklist' ); + } + + // Add user's blacklist to dictionary if user wasn't already there + self::$blacklistByUser->set( $user->getId(), $blacklist ); + } else { + // Just get the user's blacklist if it's already there + $blacklist = self::$blacklistByUser->get( $user->getId() ); + } + return $blacklist->contains( $event->getAgent()->getName() ); + } + + /** + * @return EchoContainmentList|null + */ + protected static function getWikiBlacklist() { + $clusterCache = ObjectCache::getLocalClusterInstance(); + global $wgEchoOnWikiBlacklist; + if ( !$wgEchoOnWikiBlacklist ) { + return null; + } + if ( self::$wikiBlacklist === null ) { + self::$wikiBlacklist = new EchoCachedList( + $clusterCache, + $clusterCache->makeKey( "echo_on_wiki_blacklist" ), + new EchoOnWikiList( NS_MEDIAWIKI, $wgEchoOnWikiBlacklist ) + ); + } + + return self::$wikiBlacklist; } /** @@ -227,6 +280,7 @@ * @return boolean True when the event agent is in the user whitelist */ public static function isWhitelistedByUser( EchoEvent $event, User $user ) { + $clusterCache = ObjectCache::getLocalClusterInstance(); global $wgEchoPerUserWhitelistFormat; if ( $wgEchoPerUserWhitelistFormat === null || !$event->getAgent() ) { @@ -238,18 +292,26 @@ return false; // anonymous user } - if ( !isset( self::$userWhitelist[$userId] ) ) { - self::$userWhitelist[$userId] = new EchoContainmentSet; - self::$userWhitelist[$userId]->addOnWiki( - NS_USER, - sprintf( $wgEchoPerUserWhitelistFormat, $user->getName() ), - ObjectCache::getLocalClusterInstance(), - wfMemcKey( "echo_on_wiki_whitelist_" . $userId ) - ); + // Ensure we have a list of whitelists + if ( self::$whitelistByUser === null ) { + self::$whitelistByUser = new MapCacheLRU( self::$maxRecipientCacheSize ); } - return self::$userWhitelist[$userId] - ->contains( $event->getAgent()->getName() ); + // Ensure we have a whitelist for the user + if ( !self::$whitelistByUser->has( $userId ) ) { + $whitelist = new EchoContainmentSet( $user ); + self::$whitelistByUser->set( $userId, $whitelist ); + $whitelist->addOnWiki( + NS_USER, + sprintf( $wgEchoPerUserWhitelistFormat, $user->getName() ), + $clusterCache, + $clusterCache->makeKey( "echo_on_wiki_whitelist_" . $userId ) + ); + } else { + // Just get the user's whitelist + $whitelist = self::$whitelistByUser->get( $userId ); + } + return $whitelist->contains( $event->getAgent()->getName() ); } /** @@ -369,14 +431,14 @@ } ); } - // 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 ); - } ); - } + // Apply blacklists and whitelists. + $notify->addFilter( function ( $user ) use ( $event ) { + if ( self::isBlacklistedByUser( $event, $user ) && $event->getTitle()->getNamespace() !== NS_USER_TALK ) { + return self::isWhitelistedByUser( $event, $user ); + } + + return true; + } ); return $notify->getIterator(); } diff --git a/tests/phpunit/ContainmentSetTest.php b/tests/phpunit/ContainmentSetTest.php index 1c511e1..01efcb4 100644 --- a/tests/phpunit/ContainmentSetTest.php +++ b/tests/phpunit/ContainmentSetTest.php @@ -6,7 +6,7 @@ class ContainmentSetTest extends MediaWikiTestCase { public function testGenericContains() { - $list = new EchoContainmentSet(); + $list = new EchoContainmentSet( self::getTestUser()->getUser() ); $list->addArray( [ 'foo', 'bar' ] ); $this->assertTrue( $list->contains( 'foo' ) ); -- To view, visit https://gerrit.wikimedia.org/r/320718 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ibf548da4aa600bdc7848cba1947436e56ac48a4a Gerrit-PatchSet: 23 Gerrit-Project: mediawiki/extensions/Echo Gerrit-Branch: master Gerrit-Owner: Legoktm <[email protected]> Gerrit-Reviewer: Catrope <[email protected]> Gerrit-Reviewer: EBernhardson <[email protected]> Gerrit-Reviewer: Jforrester <[email protected]> Gerrit-Reviewer: Legoktm <[email protected]> Gerrit-Reviewer: Mattflaschen <[email protected]> Gerrit-Reviewer: Mooeypoo <[email protected]> Gerrit-Reviewer: MtDu <[email protected]> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
