mediawiki-extensions-Echo/includes/AttributeManager.php
Matthew Flaschen 5ecc6aa7c8 BREAKING CHANGE: Change $wgEchoDefaultNotificationTypes to be logical
Merge and deploy at the *same time* as:
* BounceHandler - I3c669945080d8e1f67880bd8a31af7f88a70904d
* mediawiki-config - I13817c139967ed9e230cfb0c87c5de66da793c96

Despite claiming to be about categories, $wgEchoDefaultNotificationTypes
was actually configuring both categories and types (which go inside
categories).

For example, 'thank-you-edit' is a type, but 'emailuser' is both
a category and a type (when used as a category, this has special
effects at Special:Preferences).

Since types and categories can and sometimes do have the same names,
this leaves no way to properly and clearly configure them.  It also
makes it difficult to document what is going on (as required by
T132127).

Split into three variables:

$wgDefaultNotifyTypeAvailability - Applies unless overriden

$wgNotifyTypeAvailabilityByCategory - By category; this can be and is
displayed at Special:Preferences

$wgNotifyTypeAvailabilityByNotificationType - By type; this cannot
be displayed at Special:Preferences. To avoid confusing the user,
we introduce a restriction (which was previously followed in practice,
AFAICT) that types can only be overridden if the category is not
displayed in preferences.

Otherwise, it can look to the user like a category is on/off, but the
types within might have the opposite state.

Due to this configuration change, this is a breaking change, and needs
coordinated deployments.

This also lays the groundwork for T132127

Also change terminology to consistently use "notify type" for web/email.

It was mixing between that and output format (which unfortunately
sounds like the API format, e.g. 'model').

Bug: T132820
Bug: T132127
Change-Id: I09f39f5fc5f13f3253af9f7819bca81f1601da93
2016-04-22 19:08:12 -07:00

383 lines
10 KiB
PHP

<?php
/**
* An object that manages attributes of echo notifications: category, elegibility,
* group, section etc.
*/
class EchoAttributeManager {
/**
* @var array
*/
protected $notifications;
/**
* @var array
*/
protected $categories;
/**
* @var array
*/
protected $defaultNotifyTypeAvailability;
/**
* @var array
*/
protected $notifyTypeAvailabilityByCategory;
/**
* @var array
*/
protected $dismissabilityByCategory;
/**
* @var array
*/
protected $notifiers;
/**
* Notification section constant
*/
const ALERT = 'alert';
const MESSAGE = 'message';
const ALL = 'all';
/**
* Notifications are broken down to two sections, default is alert
* @var array
*/
public static $sections = array(
self::ALERT,
self::MESSAGE
);
/**
* Names for keys in $wgEchoNotifications notification config
*/
const ATTR_LOCATORS = 'user-locators';
const ATTR_FILTERS = 'user-filters';
/**
* An EchoAttributeManager instance created from global variables
* @param EchoAttributeManager
*/
protected static $globalVarInstance = null;
/**
* @param array $notifications Notification attributes
* @param array $categories Notification categories
* @param array $defaultNotifyTypeAvailability Associative array with output
* formats as keys and whether they are available as boolean values.
* @param array $notifyTypeAvailabilityByCategory Associative array with
* categories as keys and value an associative array as with
* $defaultNotifyTypeAvailability.
* @param array $notifiers Associative array mapping notify types to notifier
* that handles them
*/
public function __construct( array $notifications, array $categories, array $defaultNotifyTypeAvailability, array $notifyTypeAvailabilityByCategory, array $notifiers ) {
// Extensions can define their own notifications and categories
$this->notifications = $notifications;
$this->categories = $categories;
$this->defaultNotifyTypeAvailability = $defaultNotifyTypeAvailability;
$this->notifyTypeAvailabilityByCategory = $notifyTypeAvailabilityByCategory;
$this->dismissabilityByCategory = null;
$this->notifiers = $notifiers;
}
/**
* Create an instance from global variables
* @return EchoAttributeManager
*/
public static function newFromGlobalVars() {
global $wgEchoNotifications, $wgEchoNotificationCategories, $wgDefaultNotifyTypeAvailability, $wgNotifyTypeAvailabilityByCategory, $wgEchoNotifiers;
// Unit test may alter the global data for test purpose
if ( defined( 'MW_PHPUNIT_TEST' ) ) {
return new self( $wgEchoNotifications, $wgEchoNotificationCategories, $wgDefaultNotifyTypeAvailability, $wgNotifyTypeAvailabilityByCategory, $wgEchoNotifiers );
}
if ( self::$globalVarInstance === null ) {
self::$globalVarInstance = new self(
$wgEchoNotifications,
$wgEchoNotificationCategories,
$wgDefaultNotifyTypeAvailability,
$wgNotifyTypeAvailabilityByCategory,
$wgEchoNotifiers
);
}
return self::$globalVarInstance;
}
/**
* Get the user-locators|user-filters related to the provided event type
*
* @param string $type
* @param string $locator Either self::ATTR_LOCATORS or self::ATTR_FILTERS
* @return array
*/
public function getUserCallable( $type, $locator = self::ATTR_LOCATORS ) {
if ( isset( $this->notifications[$type][$locator] ) ) {
return (array)$this->notifications[$type][$locator];
} else {
return array();
}
}
/**
* Get the enabled events for a user, which excludes user-dismissed events
* from the general enabled events
* @param User
* @param string web/email
* @return string[]
*/
public function getUserEnabledEvents( User $user, $notifyType ) {
$eventTypesToLoad = $this->notifications;
foreach ( $eventTypesToLoad as $eventType => $eventData ) {
$category = $this->getNotificationCategory( $eventType );
// Make sure the user is eligible to recieve this type of notification
if ( !$this->getCategoryEligibility( $user, $category ) ) {
unset( $eventTypesToLoad[$eventType] );
}
if ( !$user->getOption( 'echo-subscriptions-' . $notifyType . '-' . $category ) ) {
unset( $eventTypesToLoad[$eventType] );
}
}
$eventTypes = array_keys( $eventTypesToLoad );
return $eventTypes;
}
/**
* Get the uesr enabled events for the specified sections
* @param User
* @param string
* @param string[]
* @return string[]
*/
public function getUserEnabledEventsbySections( User $user, $notifyType, array $sections ) {
$events = array();
foreach ( $sections as $section ) {
$events = array_merge(
$events,
call_user_func(
array( $this, 'get' . ucfirst( $section ) . 'Events' )
)
);
}
return array_intersect(
$this->getUserEnabledEvents( $user, $notifyType ),
$events
);
}
/**
* Get alert notification event. Notifications without a section attributes
* default to section alert
* @return array
*/
public function getAlertEvents() {
$events = array();
foreach ( $this->notifications as $event => $attribs ) {
if (
!isset( $attribs['section'] )
|| !in_array( $attribs['section'], self::$sections )
|| $attribs['section'] === 'alert'
) {
$events[] = $event;
}
}
return $events;
}
/**
* Get message notification event
* @return array
*/
public function getMessageEvents() {
$events = array();
foreach ( $this->notifications as $event => $attribs ) {
if (
isset( $attribs['section'] )
&& $attribs['section'] === 'message'
) {
$events[] = $event;
}
}
return $events;
}
/**
* Gets array of internal category names
*
* @return All internal names
*/
public function getInternalCategoryNames() {
return array_keys( $this->categories );
}
/**
* See if a user is eligible to recieve a certain type of notification
* (based on user groups, not user preferences)
*
* @param User
* @param string A notification category defined in $wgEchoNotificationCategories
* @return boolean
*/
public function getCategoryEligibility( $user, $category ) {
$usersGroups = $user->getGroups();
if ( isset( $this->categories[$category]['usergroups'] ) ) {
$allowedGroups = $this->categories[$category]['usergroups'];
if ( !array_intersect( $usersGroups, $allowedGroups ) ) {
return false;
}
}
return true;
}
/**
* Get the priority for a specific notification type
*
* @param string A notification type defined in $wgEchoNotifications
* @return integer From 1 to 10 (10 is default)
*/
public function getNotificationPriority( $notificationType ) {
$category = $this->getNotificationCategory( $notificationType );
return $this->getCategoryPriority( $category );
}
/**
* Get the priority for a notification category
*
* @param string A notification category defined in $wgEchoNotificationCategories
* @return integer From 1 to 10 (10 is default)
*/
public function getCategoryPriority( $category ) {
if ( isset( $this->categories[$category]['priority'] ) ) {
$priority = $this->categories[$category]['priority'];
if ( $priority >= 1 && $priority <= 10 ) {
return $priority;
}
}
return 10;
}
/**
* Get the notification category for a notification type
*
* @param string A notification type defined in $wgEchoNotifications
* @return string The name of the notification category or 'other' if no
* category is explicitly assigned.
*/
public function getNotificationCategory( $notificationType ) {
if ( isset( $this->notifications[$notificationType]['category'] ) ) {
$category = $this->notifications[$notificationType]['category'];
if ( isset( $this->categories[$category] ) ) {
return $category;
}
}
return 'other';
}
/**
* Gets an associative array mapping categories to the notification types in
* the category
*
* @return array Associative array with category as key
*/
public function getEventsByCategory() {
$eventsByCategory = array();
foreach ( $this->categories as $category => $categoryDetails ) {
$eventsByCategory[$category] = array();
}
foreach ( $this->notifications as $notificationType => $notificationDetails ) {
$category = $notificationDetails['category'];
if ( isset( $eventsByCategory[$category] ) ) {
// Only real categories. Currently, this excludes the 'foreign'
// psuedo-category.
$eventsByCategory[$category][] = $notificationType;
}
}
return $eventsByCategory;
}
/**
* Checks whether the specified notify type is available for the specified
* category.
*
* This means whether users *can* turn notifications for this category and format
* on, regardless of the default or a particular user's preferences.
*
* @param string $category Category name
* @param string $notifyType notify type, e.g. email/web.
*/
public function isNotifyTypeAvailableForCategory( $category, $notifyType ) {
if ( isset( $this->notifyTypeAvailabilityByCategory[$category][$notifyType] ) ) {
return $this->notifyTypeAvailabilityByCategory[$category][$notifyType];
} else {
return $this->defaultNotifyTypeAvailability[$notifyType];
}
}
/**
* Checks whether category is displayed in preferences
*
* @param string $category Category name
*/
public function isCategoryDisplayedInPreferences( $category ) {
return !(
isset( $this->categories[$category]['no-dismiss'] ) &&
in_array( 'all', $this->categories[$category]['no-dismiss'] )
);
}
/**
* Checks whether the specified notify type is dismissable for the specified
* category.
*
* This means whether the user is allowed to opt out of receiving notifications
* for this category and format.
*
* @param string $category Name of category
* @param string $notifyType notify type, e.g. email/web.
*/
public function isNotifyTypeDismissableForCategory( $category, $notifyType ) {
return !(
isset( $this->categories[$category]['no-dismiss'] ) &&
(
in_array( 'all', $this->categories[$category]['no-dismiss'] ) ||
in_array( $notifyType, $this->categories[$category]['no-dismiss'] )
)
);
}
/**
* Get notification section for a notification type
* @todo add a unit test case
* @param string $notificationType
* @return string
*/
public function getNotificationSection( $notificationType ) {
if ( isset( $this->notifications[$notificationType]['section'] ) ) {
return $this->notifications[$notificationType]['section'];
}
return 'alert';
}
}