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

Reply via email to