From d27f32f9b1450db49798855acdf6d06c6499a588 Mon Sep 17 00:00:00 2001 From: Matthew Flaschen Date: Mon, 18 Apr 2016 23:06:03 -0400 Subject: [PATCH] Unlisted special page for displaying notification configuration This provides a simple unlisted special page, Special:DisplayNotificationsConfiguration, to document how the notifications are structured and configured. It shows: * Which types are in each category * Which notify types (web/email) can be enabled for each category * Which notify types are mandatory for each category * Which notify types are enabled by default: ** For existing users ** For new users Bug: T132127 Change-Id: I25b447a69a7c984941dfd703345d7977c0000bfe --- Echo.alias.php | 1 + Echo.php | 1 + autoload.php | 1 + i18n/en.json | 11 + i18n/qqq.json | 11 + ...ecialDisplayNotificationsConfiguration.php | 268 ++++++++++++++++++ 6 files changed, 293 insertions(+) create mode 100644 includes/special/SpecialDisplayNotificationsConfiguration.php diff --git a/Echo.alias.php b/Echo.alias.php index 99098adfc..1aaedd35f 100644 --- a/Echo.alias.php +++ b/Echo.alias.php @@ -12,6 +12,7 @@ $specialPageAliases = array(); /** English (English) */ $specialPageAliases['en'] = array( 'Notifications' => array( 'Notifications' ), + 'DisplayNotificationsConfiguration' => array( 'DisplayNotificationsConfiguration' ), ); /** Arabic (العربية) */ diff --git a/Echo.php b/Echo.php index 422eeaa39..0c2081d77 100644 --- a/Echo.php +++ b/Echo.php @@ -62,6 +62,7 @@ $wgAPIModules['echomarkseen'] = 'ApiEchoMarkSeen'; // Special page $wgSpecialPages['Notifications'] = 'SpecialNotifications'; +$wgSpecialPages['DisplayNotificationsConfiguration'] = 'SpecialDisplayNotificationsConfiguration'; // Housekeeping hooks $wgHooks['LoadExtensionSchemaUpdates'][] = 'EchoHooks::onLoadExtensionSchemaUpdates'; diff --git a/autoload.php b/autoload.php index 6d636c624..96a163f69 100644 --- a/autoload.php +++ b/autoload.php @@ -107,6 +107,7 @@ $wgAutoloadClasses += array( 'MWEchoNotificationEmailBundleJob' => __DIR__ . '/includes/jobs/NotificationEmailBundleJob.php', 'NotificationControllerTest' => __DIR__ . '/tests/phpunit/controller/NotificationControllerTest.php', 'NotificationsTest' => __DIR__ . '/tests/NotificationsTest.php', + 'SpecialDisplayNotificationsConfiguration' => __DIR__ . '/includes/special/SpecialDisplayNotificationsConfiguration.php', 'SpecialNotifications' => __DIR__ . '/includes/special/SpecialNotifications.php', 'SpecialNotificationsFormatter' => __DIR__ . '/includes/formatters/SpecialNotificationsFormatter.php', 'SuppressionMaintenanceTest' => __DIR__ . '/tests/phpunit/maintenance/SupressionMaintenanceTest.php', diff --git a/i18n/en.json b/i18n/en.json index 258e31e63..3e79afec4 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -80,6 +80,17 @@ "notifications": "Notifications", "tooltip-pt-notifications-alert": "{{GENDER:|Your}} alerts", "tooltip-pt-notifications-message": "{{GENDER:|Your}} messages", + "echo-displaynotificationsconfiguration": "Display Notifications configuration", + "echo-displaynotificationsconfiguration-summary": "This is an overview of how Notifications are configured on this wiki.", + "echo-displaynotificationsconfiguration-notifications-by-category-header": "Notifications by category", + "echo-displaynotificationsconfiguration-available-notification-methods-header": "Allowed notification methods", + "echo-displaynotificationsconfiguration-available-notification-methods-by-category-legend": "Which notification methods are supported for each category", + "echo-displaynotificationsconfiguration-available-notification-methods-by-type-legend": "Which notification methods are supported for each type; only applies to types within categories that are hidden from preferences", + "echo-displaynotificationsconfiguration-enabled-default-header": "Enabled by default", + "echo-displaynotificationsconfiguration-enabled-default-existing-users-legend": "Existing users", + "echo-displaynotificationsconfiguration-enabled-default-new-users-legend": "New users", + "echo-displaynotificationsconfiguration-mandatory-notification-methods-header": "Required notification methods", + "echo-displaynotificationsconfiguration-mandatory-notification-methods-by-category-legend": "Which notification methods are mandatory for each category", "echo-specialpage": "Notifications", "echo-anon": "To receive notifications, [$1 create an account] or [$2 log in].", "echo-none": "You have no notifications.", diff --git a/i18n/qqq.json b/i18n/qqq.json index ecfd82b8d..375f84fb4 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -69,6 +69,17 @@ "echo-no-title": "Shown in place of a page title in a notification if the notification has no specified page title.", "echo-error-no-formatter": "Error message displayed when no formatting has been defined for a notification. In other words, the extension doesn't know how to properly display the notification.", "notifications": "{{doc-special|Notifications}}\n{{Identical|Notification}}", + "echo-displaynotificationsconfiguration": "{{doc-special|DisplayNotificationsConfiguration}}", + "echo-displaynotificationsconfiguration-summary": "Summary of the DisplayNotificationsConfiguration special page", + "echo-displaynotificationsconfiguration-notifications-by-category-header": "Header on DisplayNotificationsConfiguration for a list of which notifications are in each category", + "echo-displaynotificationsconfiguration-available-notification-methods-header": "Header on DisplayNotificationsConfiguration for section regarding methods the user can use to receive different kinds of notifications", + "echo-displaynotificationsconfiguration-available-notification-methods-by-category-legend": "Explanatory text on DisplayNotificationsConfiguration regarding which notification methods are available for each category", + "echo-displaynotificationsconfiguration-available-notification-methods-by-type-legend": "Explanatory text on DisplayNotificationsConfiguration regarding which notification methods are available for each type", + "echo-displaynotificationsconfiguration-enabled-default-header": "Header on DisplayNotificationsConfiguration for section about which categories are enabled by default", + "echo-displaynotificationsconfiguration-enabled-default-existing-users-legend": "Explanatory text on DisplayNotificationsConfiguration regarding which notification methods for each category are enabled by default, for existing users", + "echo-displaynotificationsconfiguration-enabled-default-new-users-legend": "Explanatory text on DisplayNotificationsConfiguration regarding which notification methods for each category are enabled by default, for new users", + "echo-displaynotificationsconfiguration-mandatory-notification-methods-header": "Header on DisplayNotificationsConfiguration for section about which notification methods are required", + "echo-displaynotificationsconfiguration-mandatory-notification-methods-by-category-legend": "Explanatory text on DisplayNotificationsConfiguration regarding which notification methods are mandatory for each category", "tooltip-pt-notifications-alert": "This is used for the title (mouseover text) of the alert notifications user tool.", "tooltip-pt-notifications-message": "This is used for the title (mouseover text) of the message notifications user tool.", "echo-specialpage": "Special page title for Special:Notifications.\n{{Identical|Notification}}", diff --git a/includes/special/SpecialDisplayNotificationsConfiguration.php b/includes/special/SpecialDisplayNotificationsConfiguration.php new file mode 100644 index 000000000..cff5b2941 --- /dev/null +++ b/includes/special/SpecialDisplayNotificationsConfiguration.php @@ -0,0 +1,268 @@ +attributeManager = EchoAttributeManager::newFromGlobalVars(); + $this->notificationController = new EchoNotificationController(); + } + + public function execute( $subPage ) { + global $wgEchoNotifiers; + + $this->setHeaders(); + $this->checkPermissions(); + + $internalCategoryNames = $this->attributeManager->getInternalCategoryNames(); + $this->categoryNames = array(); + + foreach ( $internalCategoryNames as $internalCategoryName ) { + $formattedFriendlyCategoryName = Html::element( + 'strong', + array(), + $this->msg( 'echo-category-title-' . $internalCategoryName )->numParams( 1 )->text() + ); + + $formattedInternalCategoryName = $this->msg( 'parentheses' )->rawParams( + Html::element( + 'em', + array(), + $internalCategoryName + ) + )->parse(); + + $this->categoryNames[$internalCategoryName] = $formattedFriendlyCategoryName . ' ' . $formattedInternalCategoryName; + } + + $this->flippedCategoryNames = array_flip( $this->categoryNames ); + + $this->notifyTypes = array(); + foreach ( $wgEchoNotifiers as $notifyType => $notifier ) { + $this->notifyTypes[$notifyType] = $this->msg( 'echo-pref-' . $notifyType )->text(); + } + + $this->flippedNotifyTypes = array_flip( $this->notifyTypes ); + + $this->getOutput()->setPageTitle( $this->msg( 'echo-displaynotificationsconfiguration' )->text() ); + $this->outputHeader( 'echo-displaynotificationsconfiguration-summary' ); + $this->outputConfiguration(); + } + + /** + * Outputs the Echo configuration + */ + protected function outputConfiguration() { + $this->outputNotificationsInCategories(); + $this->outputAvailability(); + $this->outputMandatory(); + $this->outputEnabledDefault(); + } + + /** + * Displays a checkbox matrix, using an HTMLForm + * + * @param string $id Arbitrary ID + * @param string $legendMsgKey Message key for an explanatory legend. For example, + * "We wrote this feature because in the days of yore, there was but one notification badge" + * @param array $rowLabelMapping Associative array mapping label to tag + * @param array $columnLabelMapping Associative array mapping label to tag + * @param array $value Array consisting of strings in the format '$columnTag-$rowTag' + */ + protected function outputCheckMatrix( $id, $legendMsgKey, array $rowLabelMapping, array $columnLabelMapping, array $value ) { + $form = new HTMLForm( + array( + $id => array( + 'type' => 'checkmatrix', + 'rows' => $rowLabelMapping, + 'columns' => $columnLabelMapping, + 'default' => $value, + 'disabled' => true, + ) + ), + $this + ); + + $form->setTitle( $this->getTitle() ) + ->prepareForm() + ->suppressDefaultSubmit() + ->setWrapperLegendMsg( $legendMsgKey ) + ->displayForm( false ); + } + + /** + * Outputs the notification types in each category + */ + protected function outputNotificationsInCategories() { + $notificationsByCategory = $this->attributeManager->getEventsByCategory(); + + $out = $this->getOutput(); + $out->addHTML( + Html::element( + 'h2', + array(), + $this->msg( 'echo-displaynotificationsconfiguration-notifications-by-category-header' )->text() + ) + ); + + $out->addHTML( Html::openElement( 'ul' ) ); + foreach ( $notificationsByCategory as $categoryName => $notificationTypes ) { + $implodedTypes = Html::element( + 'span', + array(), + implode( $this->msg( 'comma-separator' )->text(), $notificationTypes ) + ); + + $out->addHTML( + Html::rawElement( + 'li', + array(), + $this->categoryNames[$categoryName] . $this->msg( 'colon-separator' )->text() . ' ' . $implodedTypes + ) + ); + } + $out->addHTML( Html::closeElement( 'ul' ) ); + } + + /** + * Output which notify types are available for each category + */ + protected function outputAvailability() { + global $wgEchoNotifications; + + $this->getOutput()->addHTML( Html::element( 'h2', array(), $this->msg( 'echo-displaynotificationsconfiguration-available-notification-methods-header' )->text() ) ); + + $byCategoryValue = array(); + + foreach ( $this->notifyTypes as $notifyType => $displayNotifyType ) { + foreach ( $this->categoryNames as $category => $displayCategory ) { + if ( $this->attributeManager->isNotifyTypeAvailableForCategory( $category, $notifyType ) ) { + $byCategoryValue[] = "$notifyType-$category"; + } + } + } + + $this->outputCheckMatrix( 'availability-by-category', 'echo-displaynotificationsconfiguration-available-notification-methods-by-category-legend', $this->flippedCategoryNames, $this->flippedNotifyTypes, $byCategoryValue ); + + $byTypeValue = array(); + + $specialNotificationTypes = array_keys( array_filter( $wgEchoNotifications, function ( $val ) { + return isset( $val['notify-type-availability'] ); + } ) ); + foreach ( $specialNotificationTypes as $notificationType ) { + $allowedNotifyTypes = $this->notificationController->getEventNotifyTypes( $notificationType ); + foreach ( $allowedNotifyTypes as $notifyType ) { + $byTypeValue[] = "$notifyType-$notificationType"; + } + } + + // No user-friendly label for rows yet + $this->outputCheckMatrix( 'availability-by-type', 'echo-displaynotificationsconfiguration-available-notification-methods-by-type-legend', array_combine( $specialNotificationTypes, $specialNotificationTypes ), $this->flippedNotifyTypes, $byTypeValue ); + } + + /** + * Output which notification categories are turned on by default, for each notify type + */ + protected function outputEnabledDefault() { + $this->getOutput()->addHTML( Html::element( 'h2', array(), $this->msg( 'echo-displaynotificationsconfiguration-enabled-default-header' )->text() ) ); + + // In reality, anon users are not relevant to Echo, but this lets us easily query default options. + $anonUser = new User; + + $byCategoryValueExisting = array(); + foreach ( $this->notifyTypes as $notifyType => $displayNotifyType ) { + foreach ( $this->categoryNames as $category => $displayCategory ) { + $tag = "$notifyType-$category"; + if ( $anonUser->getOption( "echo-subscriptions-$tag" ) ) { + $byCategoryValueExisting[] = "$notifyType-$category"; + } + } + } + + $this->outputCheckMatrix( 'enabled-by-default-generic', 'echo-displaynotificationsconfiguration-enabled-default-existing-users-legend', $this->flippedCategoryNames, $this->flippedNotifyTypes, $byCategoryValueExisting ); + + $loggedInUser = new User; + // This might not catch if there are other hooks that do similar. + // We can't run the actual hook, to avoid side effects. + $overrides = EchoHooks::getNewUserPreferenceOverrides(); + foreach ( $overrides as $prefKey => $value ) { + $loggedInUser->setOption( $prefKey, $value ); + } + + $byCategoryValueNew = array(); + foreach ( $this->notifyTypes as $notifyType => $displayNotifyType ) { + foreach ( $this->categoryNames as $category => $displayCategory ) { + $tag = "$notifyType-$category"; + if ( $loggedInUser->getOption( "echo-subscriptions-$tag" ) ) { + $byCategoryValueNew[] = "$notifyType-$category"; + } + } + } + + $this->outputCheckMatrix( 'enabled-by-default-new', 'echo-displaynotificationsconfiguration-enabled-default-new-users-legend', $this->flippedCategoryNames, $this->flippedNotifyTypes, $byCategoryValueNew ); + + } + + /** + * Output which notify types are mandatory for each category + */ + protected function outputMandatory() { + $byCategoryValue = array(); + + $this->getOutput()->addHTML( Html::element( 'h2', array(), $this->msg( 'echo-displaynotificationsconfiguration-mandatory-notification-methods-header' )->text() ) ); + + foreach ( $this->notifyTypes as $notifyType => $displayNotifyType ) { + foreach ( $this->categoryNames as $category => $displayCategory ) { + if ( !$this->attributeManager->isNotifyTypeDismissableForCategory( $category, $notifyType ) ) { + $byCategoryValue[] = "$notifyType-$category"; + } + } + } + + $this->outputCheckMatrix( 'mandatory', 'echo-displaynotificationsconfiguration-mandatory-notification-methods-by-category-legend', $this->flippedCategoryNames, $this->flippedNotifyTypes, $byCategoryValue ); + + } +}