2012-04-27 15:14:24 +00:00
|
|
|
<?php
|
|
|
|
|
2022-04-08 00:28:15 +00:00
|
|
|
namespace MediaWiki\Extension\Notifications;
|
|
|
|
|
|
|
|
use ApiModuleManager;
|
|
|
|
use Config;
|
|
|
|
use Content;
|
|
|
|
use DatabaseUpdater;
|
|
|
|
use DeferredUpdates;
|
|
|
|
use EchoAttributeManager;
|
|
|
|
use EchoDiscussionParser;
|
|
|
|
use EchoEmailFormat;
|
|
|
|
use EchoEmailFrequency;
|
|
|
|
use EchoEvent;
|
|
|
|
use EchoNotification;
|
|
|
|
use EchoSeenTime;
|
|
|
|
use EchoServices;
|
|
|
|
use EmailNotification;
|
|
|
|
use ExtensionRegistry;
|
|
|
|
use Hooks as MWHooks;
|
|
|
|
use HTMLCheckMatrix;
|
|
|
|
use LinksUpdate;
|
|
|
|
use LogEntry;
|
|
|
|
use MailAddress;
|
2022-08-22 22:46:42 +00:00
|
|
|
use MediaWiki\Api\Hook\ApiMain__moduleManagerHook;
|
|
|
|
use MediaWiki\Auth\Hook\LocalUserCreatedHook;
|
2022-05-01 15:16:32 +00:00
|
|
|
use MediaWiki\DAO\WikiAwareEntity;
|
2022-11-02 03:51:15 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Controller\ModerationController;
|
|
|
|
use MediaWiki\Extension\Notifications\Controller\NotificationController;
|
2022-11-01 22:01:23 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Formatters\EchoEventPresentationModel;
|
2022-11-02 20:47:04 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Mapper\EventMapper;
|
|
|
|
use MediaWiki\Extension\Notifications\Mapper\NotificationMapper;
|
2022-04-08 00:47:34 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Push\Api\ApiEchoPushSubscriptions;
|
2022-08-22 22:46:42 +00:00
|
|
|
use MediaWiki\Hook\AbortTalkPageEmailNotificationHook;
|
2022-08-22 22:31:10 +00:00
|
|
|
use MediaWiki\Hook\BeforePageDisplayHook;
|
|
|
|
use MediaWiki\Hook\EmailUserCompleteHook;
|
2022-08-22 22:46:42 +00:00
|
|
|
use MediaWiki\Hook\GetNewMessagesAlertHook;
|
|
|
|
use MediaWiki\Hook\LinksUpdateCompleteHook;
|
2022-08-22 22:36:48 +00:00
|
|
|
use MediaWiki\Hook\LoginFormValidErrorMessagesHook;
|
|
|
|
use MediaWiki\Hook\OutputPageCheckLastModifiedHook;
|
2022-10-12 00:50:11 +00:00
|
|
|
use MediaWiki\Hook\PreferencesGetIconHook;
|
2020-06-03 09:20:46 +00:00
|
|
|
use MediaWiki\Hook\RecentChange_saveHook;
|
2022-08-22 22:46:42 +00:00
|
|
|
use MediaWiki\Hook\SendWatchlistEmailNotificationHook;
|
|
|
|
use MediaWiki\Hook\SkinTemplateNavigation__UniversalHook;
|
2016-02-29 06:13:25 +00:00
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
2017-03-17 11:16:28 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2022-08-22 22:36:48 +00:00
|
|
|
use MediaWiki\Page\Hook\ArticleDeleteCompleteHook;
|
|
|
|
use MediaWiki\Page\Hook\ArticleUndeleteHook;
|
2022-08-22 22:46:42 +00:00
|
|
|
use MediaWiki\Page\Hook\RollbackCompleteHook;
|
2022-08-22 22:31:10 +00:00
|
|
|
use MediaWiki\Preferences\Hook\GetPreferencesHook;
|
2020-04-20 19:40:07 +00:00
|
|
|
use MediaWiki\Preferences\MultiTitleFilter;
|
2018-07-09 22:45:54 +00:00
|
|
|
use MediaWiki\Preferences\MultiUsernameFilter;
|
2022-05-20 02:11:31 +00:00
|
|
|
use MediaWiki\ResourceLoader as RL;
|
2022-08-22 22:31:10 +00:00
|
|
|
use MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook;
|
2022-05-20 02:11:31 +00:00
|
|
|
use MediaWiki\ResourceLoader\ResourceLoader;
|
2019-04-17 15:46:06 +00:00
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
2020-06-16 04:03:45 +00:00
|
|
|
use MediaWiki\Storage\EditResult;
|
2022-08-22 22:36:48 +00:00
|
|
|
use MediaWiki\Storage\Hook\PageSaveCompleteHook;
|
2022-08-22 22:31:10 +00:00
|
|
|
use MediaWiki\User\Hook\UserClearNewTalkNotificationHook;
|
|
|
|
use MediaWiki\User\Hook\UserGetDefaultOptionsHook;
|
|
|
|
use MediaWiki\User\Hook\UserGroupsChangedHook;
|
2022-08-22 22:46:42 +00:00
|
|
|
use MediaWiki\User\Hook\UserSaveSettingsHook;
|
2022-08-22 22:31:10 +00:00
|
|
|
use MediaWiki\User\Options\Hook\LoadUserOptionsHook;
|
|
|
|
use MediaWiki\User\Options\Hook\SaveUserOptionsHook;
|
2020-04-20 23:03:54 +00:00
|
|
|
use MediaWiki\User\UserIdentity;
|
2022-04-08 00:28:15 +00:00
|
|
|
use MWEchoDbFactory;
|
|
|
|
use MWEchoNotifUser;
|
|
|
|
use MWException;
|
|
|
|
use OutputPage;
|
|
|
|
use RecentChange;
|
|
|
|
use ResourceLoaderEchoImageModule;
|
|
|
|
use Skin;
|
|
|
|
use SkinTemplate;
|
|
|
|
use SpecialPage;
|
|
|
|
use Title;
|
|
|
|
use UpdateEchoSchemaForSuppression;
|
|
|
|
use User;
|
|
|
|
use WebRequest;
|
|
|
|
use WikiMap;
|
|
|
|
use WikiPage;
|
|
|
|
|
2022-08-22 22:31:10 +00:00
|
|
|
class Hooks implements
|
2022-08-22 22:46:42 +00:00
|
|
|
AbortTalkPageEmailNotificationHook,
|
|
|
|
ApiMain__moduleManagerHook,
|
2022-08-22 22:36:48 +00:00
|
|
|
ArticleDeleteCompleteHook,
|
|
|
|
ArticleUndeleteHook,
|
2022-08-22 22:31:10 +00:00
|
|
|
BeforePageDisplayHook,
|
|
|
|
EmailUserCompleteHook,
|
2022-08-22 22:46:42 +00:00
|
|
|
GetNewMessagesAlertHook,
|
2022-08-22 22:31:10 +00:00
|
|
|
GetPreferencesHook,
|
2022-08-22 22:46:42 +00:00
|
|
|
LinksUpdateCompleteHook,
|
2022-08-22 22:31:10 +00:00
|
|
|
LoadUserOptionsHook,
|
2022-08-22 22:46:42 +00:00
|
|
|
LocalUserCreatedHook,
|
2022-08-22 22:36:48 +00:00
|
|
|
LoginFormValidErrorMessagesHook,
|
|
|
|
OutputPageCheckLastModifiedHook,
|
|
|
|
PageSaveCompleteHook,
|
2022-08-22 22:31:10 +00:00
|
|
|
PreferencesGetIconHook,
|
|
|
|
RecentChange_saveHook,
|
|
|
|
ResourceLoaderRegisterModulesHook,
|
2022-08-22 22:46:42 +00:00
|
|
|
RollbackCompleteHook,
|
2022-08-22 22:31:10 +00:00
|
|
|
SaveUserOptionsHook,
|
2022-08-22 22:46:42 +00:00
|
|
|
SendWatchlistEmailNotificationHook,
|
|
|
|
SkinTemplateNavigation__UniversalHook,
|
2022-08-22 22:31:10 +00:00
|
|
|
UserClearNewTalkNotificationHook,
|
|
|
|
UserGetDefaultOptionsHook,
|
2022-08-22 22:46:42 +00:00
|
|
|
UserGroupsChangedHook,
|
|
|
|
UserSaveSettingsHook
|
2022-08-22 22:31:10 +00:00
|
|
|
{
|
2020-06-03 09:20:46 +00:00
|
|
|
/**
|
|
|
|
* @var Config
|
|
|
|
*/
|
|
|
|
private $config;
|
|
|
|
|
2021-12-08 05:19:00 +00:00
|
|
|
/** @var array */
|
|
|
|
private static $revertedRevIds = [];
|
|
|
|
|
2020-06-03 09:20:46 +00:00
|
|
|
public function __construct( Config $config ) {
|
|
|
|
$this->config = $config;
|
|
|
|
}
|
2018-03-26 22:32:30 +00:00
|
|
|
|
2018-07-26 00:19:17 +00:00
|
|
|
/**
|
|
|
|
* @param array &$defaults
|
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public function onUserGetDefaultOptions( &$defaults ) {
|
2021-08-18 21:47:33 +00:00
|
|
|
global $wgAllowHTMLEmail, $wgEchoNotificationCategories, $wgEchoEnablePush;
|
2016-12-01 00:17:30 +00:00
|
|
|
|
|
|
|
if ( $wgAllowHTMLEmail ) {
|
2018-07-26 00:19:17 +00:00
|
|
|
$defaults['echo-email-format'] = 'html'; /*EchoHooks::EMAIL_FORMAT_HTML*/
|
2016-12-01 00:17:30 +00:00
|
|
|
} else {
|
2018-07-26 00:19:17 +00:00
|
|
|
$defaults['echo-email-format'] = 'plain-text'; /*EchoHooks::EMAIL_FORMAT_PLAIN_TEXT*/
|
2016-12-01 00:17:30 +00:00
|
|
|
}
|
|
|
|
|
2018-09-21 00:15:09 +00:00
|
|
|
$presets = [
|
|
|
|
// Set all of the events to notify by web but not email by default
|
|
|
|
// (won't affect events that don't email)
|
|
|
|
'default' => [
|
|
|
|
'email' => false,
|
|
|
|
'web' => true,
|
|
|
|
],
|
|
|
|
// most settings default to web on, email off, but override these
|
|
|
|
'system' => [
|
|
|
|
'email' => true,
|
|
|
|
],
|
|
|
|
'user-rights' => [
|
|
|
|
'email' => true,
|
|
|
|
],
|
|
|
|
'article-linked' => [
|
|
|
|
'web' => false,
|
|
|
|
],
|
|
|
|
'mention-failure' => [
|
|
|
|
'web' => false,
|
|
|
|
],
|
|
|
|
'mention-success' => [
|
|
|
|
'web' => false,
|
|
|
|
],
|
2019-12-09 02:42:55 +00:00
|
|
|
'watchlist' => [
|
|
|
|
'web' => false,
|
|
|
|
],
|
|
|
|
'minor-watchlist' => [
|
|
|
|
'web' => false,
|
|
|
|
],
|
2018-09-21 00:15:09 +00:00
|
|
|
];
|
2021-08-18 21:47:33 +00:00
|
|
|
if ( $wgEchoEnablePush ) {
|
|
|
|
$presets['default']['push'] = true;
|
|
|
|
$presets['article-linked']['push'] = false;
|
|
|
|
$presets['mention-failure']['push'] = false;
|
|
|
|
$presets['mention-success']['push'] = false;
|
|
|
|
$presets['watchlist']['push'] = false;
|
|
|
|
$presets['minor-watchlist']['push'] = false;
|
|
|
|
}
|
2018-09-21 00:15:09 +00:00
|
|
|
|
2016-12-01 00:17:30 +00:00
|
|
|
foreach ( $wgEchoNotificationCategories as $category => $categoryData ) {
|
2018-09-21 00:15:09 +00:00
|
|
|
if ( !isset( $defaults["echo-subscriptions-email-{$category}"] ) ) {
|
|
|
|
$defaults["echo-subscriptions-email-{$category}"] = $presets[$category]['email']
|
|
|
|
?? $presets['default']['email'];
|
|
|
|
}
|
|
|
|
if ( !isset( $defaults["echo-subscriptions-web-{$category}"] ) ) {
|
|
|
|
$defaults["echo-subscriptions-web-{$category}"] = $presets[$category]['web']
|
|
|
|
?? $presets['default']['web'];
|
|
|
|
}
|
2021-08-18 21:47:33 +00:00
|
|
|
if ( $wgEchoEnablePush && !isset( $defaults["echo-subscriptions-push-{$category}"] ) ) {
|
|
|
|
$defaults["echo-subscriptions-push-{$category}"] = $presets[$category]['push']
|
|
|
|
// @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
|
|
|
|
?? $presets['default']['push'];
|
|
|
|
}
|
2016-12-01 00:17:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-15 23:21:39 +00:00
|
|
|
/**
|
|
|
|
* Initialize Echo extension with necessary data, this function is invoked
|
|
|
|
* from $wgExtensionFunctions
|
|
|
|
*/
|
|
|
|
public static function initEchoExtension() {
|
2014-07-18 03:58:21 +00:00
|
|
|
global $wgEchoNotifications, $wgEchoNotificationCategories, $wgEchoNotificationIcons,
|
2019-10-04 00:01:00 +00:00
|
|
|
$wgEchoMentionStatusNotifications, $wgAllowArticleReminderNotification, $wgAPIModules,
|
2021-03-08 22:03:20 +00:00
|
|
|
$wgEchoWatchlistNotifications, $wgEchoSeenTimeCacheType, $wgMainStash, $wgEnableEmail,
|
|
|
|
$wgEnableUserEmail;
|
2013-02-16 02:20:34 +00:00
|
|
|
|
|
|
|
// allow extensions to define their own event
|
2022-04-08 00:28:15 +00:00
|
|
|
MWHooks::run( 'BeforeCreateEchoEvent',
|
2018-08-25 10:51:14 +00:00
|
|
|
[ &$wgEchoNotifications, &$wgEchoNotificationCategories, &$wgEchoNotificationIcons ] );
|
2013-02-16 02:20:34 +00:00
|
|
|
|
2016-06-21 14:42:56 +00:00
|
|
|
// Only allow mention status notifications when enabled
|
|
|
|
if ( !$wgEchoMentionStatusNotifications ) {
|
|
|
|
unset( $wgEchoNotificationCategories['mention-failure'] );
|
2016-07-21 13:00:54 +00:00
|
|
|
unset( $wgEchoNotificationCategories['mention-success'] );
|
2016-06-21 14:42:56 +00:00
|
|
|
}
|
|
|
|
|
2017-06-01 12:08:51 +00:00
|
|
|
// Only allow article reminder notifications when enabled
|
|
|
|
if ( !$wgAllowArticleReminderNotification ) {
|
|
|
|
unset( $wgEchoNotificationCategories['article-reminder'] );
|
2017-06-08 16:38:06 +00:00
|
|
|
unset( $wgAPIModules['echoarticlereminder'] );
|
2017-06-01 12:08:51 +00:00
|
|
|
}
|
2019-10-04 00:01:00 +00:00
|
|
|
|
2019-12-09 02:42:55 +00:00
|
|
|
// Only allow watchlist notifications when enabled
|
|
|
|
if ( !$wgEchoWatchlistNotifications ) {
|
|
|
|
unset( $wgEchoNotificationCategories['watchlist'] );
|
|
|
|
unset( $wgEchoNotificationCategories['minor-watchlist'] );
|
|
|
|
}
|
|
|
|
|
2021-03-08 22:03:20 +00:00
|
|
|
// Only allow user email notifications when enabled
|
|
|
|
if ( !$wgEnableEmail || !$wgEnableUserEmail ) {
|
|
|
|
unset( $wgEchoNotificationCategories['emailuser'] );
|
|
|
|
}
|
|
|
|
|
2019-10-04 00:01:00 +00:00
|
|
|
// Default $wgEchoSeenTimeCacheType to $wgMainStash
|
|
|
|
if ( $wgEchoSeenTimeCacheType === null ) {
|
|
|
|
$wgEchoSeenTimeCacheType = $wgMainStash;
|
|
|
|
}
|
2014-08-08 20:35:58 +00:00
|
|
|
}
|
2014-02-25 06:09:27 +00:00
|
|
|
|
2013-03-01 00:26:59 +00:00
|
|
|
/**
|
|
|
|
* Handler for ResourceLoaderRegisterModules hook
|
2019-11-19 03:52:30 +00:00
|
|
|
* @param ResourceLoader $resourceLoader
|
2013-03-01 00:26:59 +00:00
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ): void {
|
2017-03-02 05:01:56 +00:00
|
|
|
global $wgExtensionDirectory, $wgEchoNotificationIcons, $wgEchoSecondaryIcons;
|
2016-12-14 23:08:34 +00:00
|
|
|
$resourceLoader->register( 'ext.echo.emailicons', [
|
2021-05-07 22:03:13 +00:00
|
|
|
'class' => ResourceLoaderEchoImageModule::class,
|
2017-03-02 05:01:56 +00:00
|
|
|
'icons' => $wgEchoNotificationIcons,
|
|
|
|
'selector' => '.mw-echo-icon-{name}',
|
|
|
|
'localBasePath' => $wgExtensionDirectory,
|
|
|
|
'remoteExtPath' => 'Echo/modules'
|
|
|
|
] );
|
|
|
|
$resourceLoader->register( 'ext.echo.secondaryicons', [
|
2021-05-07 22:03:13 +00:00
|
|
|
'class' => ResourceLoaderEchoImageModule::class,
|
2017-03-02 05:01:56 +00:00
|
|
|
'icons' => $wgEchoSecondaryIcons,
|
2016-12-14 23:08:34 +00:00
|
|
|
'selector' => '.mw-echo-icon-{name}',
|
|
|
|
'localBasePath' => $wgExtensionDirectory,
|
|
|
|
'remoteExtPath' => 'Echo/modules'
|
|
|
|
] );
|
2013-03-01 00:26:59 +00:00
|
|
|
}
|
|
|
|
|
2012-05-17 00:29:37 +00:00
|
|
|
/**
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param DatabaseUpdater $updater
|
2012-05-17 00:29:37 +00:00
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public static function onLoadExtensionSchemaUpdates( $updater ) {
|
2015-06-02 00:28:25 +00:00
|
|
|
global $wgEchoCluster;
|
2020-02-11 11:29:27 +00:00
|
|
|
if ( $wgEchoCluster ) {
|
2015-06-02 00:28:25 +00:00
|
|
|
// DatabaseUpdater does not support other databases, so skip
|
|
|
|
return;
|
|
|
|
}
|
2020-08-23 00:24:28 +00:00
|
|
|
|
|
|
|
$dbType = $updater->getDB()->getType();
|
|
|
|
|
2022-05-27 17:34:43 +00:00
|
|
|
$dir = dirname( __DIR__ ) . '/sql';
|
2013-01-15 23:21:39 +00:00
|
|
|
|
2022-06-14 21:26:08 +00:00
|
|
|
$updater->addExtensionTable( 'echo_event', "$dir/$dbType/tables-generated.sql" );
|
2021-06-27 21:43:11 +00:00
|
|
|
|
2022-01-05 22:18:15 +00:00
|
|
|
// 1.33
|
2016-06-06 21:56:53 +00:00
|
|
|
// Can't use addPostDatabaseUpdateMaintenance() here because that would
|
|
|
|
// run the migration script after dropping the fields
|
2019-03-15 18:28:29 +00:00
|
|
|
$updater->addExtensionUpdate( [ 'runMaintenance', UpdateEchoSchemaForSuppression::class,
|
2016-06-06 21:56:53 +00:00
|
|
|
'extensions/Echo/maintenance/updateEchoSchemaForSuppression.php' ] );
|
|
|
|
$updater->dropExtensionField( 'echo_event', 'event_page_namespace',
|
2022-05-27 17:34:43 +00:00
|
|
|
"$dir/patch-drop-echo_event-event_page_namespace.sql" );
|
2016-06-06 21:56:53 +00:00
|
|
|
$updater->dropExtensionField( 'echo_event', 'event_page_title',
|
2022-05-27 17:34:43 +00:00
|
|
|
"$dir/patch-drop-echo_event-event_page_title.sql" );
|
2020-08-23 00:24:28 +00:00
|
|
|
if ( $dbType === 'mysql' ) {
|
2016-08-24 02:34:32 +00:00
|
|
|
$updater->dropExtensionField( 'echo_notification', 'notification_bundle_base',
|
2022-05-27 17:34:43 +00:00
|
|
|
"$dir/mysql/patch-drop-notification_bundle_base.sql" );
|
2016-08-25 23:53:24 +00:00
|
|
|
$updater->dropExtensionField( 'echo_notification', 'notification_bundle_display_hash',
|
2022-05-27 17:34:43 +00:00
|
|
|
"$dir/mysql/patch-drop-notification_bundle_display_hash.sql" );
|
2016-08-24 02:34:32 +00:00
|
|
|
}
|
2016-08-25 23:53:24 +00:00
|
|
|
$updater->dropExtensionIndex( 'echo_notification', 'echo_notification_user_hash_timestamp',
|
2022-05-27 17:34:43 +00:00
|
|
|
"$dir/patch-drop-user-hash-timestamp-index.sql" );
|
2020-05-15 17:19:03 +00:00
|
|
|
|
2022-01-05 22:18:15 +00:00
|
|
|
// 1.35
|
2022-05-27 17:34:43 +00:00
|
|
|
$updater->addExtensionTable( 'echo_push_provider', "$dir/echo_push_provider.sql" );
|
2022-04-19 21:01:10 +00:00
|
|
|
$updater->addExtensionTable( 'echo_push_subscription', "$dir/echo_push_subscription.sql" );
|
2022-01-05 22:18:15 +00:00
|
|
|
|
|
|
|
// 1.36
|
2022-05-27 17:34:43 +00:00
|
|
|
$updater->addExtensionTable( 'echo_push_topic', "$dir/echo_push_topic.sql" );
|
2022-01-05 22:18:15 +00:00
|
|
|
|
2022-04-19 21:01:10 +00:00
|
|
|
// 1.39
|
|
|
|
if ( $dbType === 'mysql' || $dbType === 'sqlite' ) {
|
2022-08-08 17:46:16 +00:00
|
|
|
$updater->addExtensionIndex( 'echo_push_subscription', 'eps_user',
|
2022-04-19 21:01:10 +00:00
|
|
|
"$dir/$dbType/patch-cleanup-push_subscription-foreign-keys-indexes.sql" );
|
|
|
|
}
|
2022-04-13 19:22:05 +00:00
|
|
|
|
|
|
|
global $wgEchoSharedTrackingCluster, $wgEchoSharedTrackingDB;
|
|
|
|
// Following tables should only be created if both cluster and database are false.
|
|
|
|
// Otherwise they are not created in the place they are accesses, because
|
|
|
|
// DatabaseUpdater does not support other databases other than main wiki schema.
|
|
|
|
if ( $wgEchoSharedTrackingCluster === false && $wgEchoSharedTrackingDB === false ) {
|
2022-06-14 21:26:08 +00:00
|
|
|
$updater->addExtensionTable( 'echo_unread_wikis', "$dir/$dbType/tables-sharedtracking-generated.sql" );
|
2022-04-13 19:22:05 +00:00
|
|
|
|
|
|
|
// 1.34 (backported) - not for sqlite, the used data type supports the new length
|
|
|
|
if ( $updater->getDB()->getType() === 'mysql' ) {
|
|
|
|
$updater->modifyExtensionField( 'echo_unread_wikis', 'euw_wiki',
|
2022-05-27 17:34:43 +00:00
|
|
|
"$dir/mysql/patch-increase-varchar-echo_unread_wikis-euw_wiki.sql" );
|
2022-04-13 19:22:05 +00:00
|
|
|
}
|
|
|
|
}
|
2013-01-15 23:21:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for EchoGetBundleRule hook, which defines the bundle rule for each notification
|
2014-05-27 18:28:37 +00:00
|
|
|
*
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param EchoEvent $event
|
|
|
|
* @param string &$bundleString Determines how the notification should be bundled, for example,
|
2013-01-15 23:21:39 +00:00
|
|
|
* talk page notification is bundled based on namespace and title, the bundle string would be
|
|
|
|
* 'edit-user-talk-' + namespace + title, email digest/email bundling would use this hash as
|
|
|
|
* a key to identify bundle-able event. For web bundling, we bundle further based on user's
|
|
|
|
* visit to the overlay, we would generate a display hash based on the hash of $bundleString
|
|
|
|
*/
|
|
|
|
public static function onEchoGetBundleRules( $event, &$bundleString ) {
|
|
|
|
switch ( $event->getType() ) {
|
|
|
|
case 'edit-user-talk':
|
|
|
|
case 'page-linked':
|
2022-07-10 00:31:22 +00:00
|
|
|
$bundleString = $event->getType();
|
2013-01-15 23:21:39 +00:00
|
|
|
if ( $event->getTitle() ) {
|
|
|
|
$bundleString .= '-' . $event->getTitle()->getNamespace()
|
2015-10-01 13:48:52 +00:00
|
|
|
. '-' . $event->getTitle()->getDBkey();
|
2013-01-15 23:21:39 +00:00
|
|
|
}
|
2015-10-01 13:48:52 +00:00
|
|
|
break;
|
2016-07-21 13:00:54 +00:00
|
|
|
case 'mention-success':
|
2016-07-29 10:15:34 +00:00
|
|
|
case 'mention-failure':
|
|
|
|
$bundleString = 'mention-status-' . $event->getExtraParam( 'revid' );
|
2016-07-21 13:00:54 +00:00
|
|
|
break;
|
2019-12-09 02:42:55 +00:00
|
|
|
case 'watchlist-change':
|
|
|
|
case 'minor-watchlist-change':
|
|
|
|
$bundleString = 'watchlist-change';
|
|
|
|
if ( $event->getTitle() ) {
|
|
|
|
$bundleString .= '-' . $event->getTitle()->getNamespace()
|
|
|
|
. '-' . $event->getTitle()->getDBkey();
|
|
|
|
}
|
|
|
|
break;
|
2013-01-15 23:21:39 +00:00
|
|
|
}
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
2012-05-17 00:29:37 +00:00
|
|
|
/**
|
|
|
|
* Handler for GetPreferences hook.
|
2018-10-23 20:35:09 +00:00
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/GetPreferences
|
2014-05-27 18:28:37 +00:00
|
|
|
*
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param User $user User to get preferences for
|
|
|
|
* @param array &$preferences Preferences array
|
2014-05-27 18:28:37 +00:00
|
|
|
*
|
|
|
|
* @throws MWException
|
2012-05-17 00:29:37 +00:00
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public function onGetPreferences( $user, &$preferences ) {
|
2016-05-25 19:03:29 +00:00
|
|
|
global $wgEchoEnableEmailBatch,
|
2013-05-01 20:48:12 +00:00
|
|
|
$wgEchoNotifiers, $wgEchoNotificationCategories, $wgEchoNotifications,
|
2019-08-02 20:14:05 +00:00
|
|
|
$wgAllowHTMLEmail, $wgEchoPollForUpdates,
|
2019-12-09 02:42:55 +00:00
|
|
|
$wgEchoCrossWikiNotifications, $wgEchoPerUserBlacklist,
|
|
|
|
$wgEchoWatchlistNotifications;
|
2012-11-16 21:03:57 +00:00
|
|
|
|
2021-02-25 01:55:40 +00:00
|
|
|
$attributeManager = EchoServices::getInstance()->getAttributeManager();
|
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-19 02:54:15 +00:00
|
|
|
|
2012-11-16 21:03:57 +00:00
|
|
|
// Show email frequency options
|
2016-12-05 18:51:07 +00:00
|
|
|
$freqOptions = [
|
2018-12-31 15:54:19 +00:00
|
|
|
'echo-pref-email-frequency-never' => EchoEmailFrequency::NEVER,
|
|
|
|
'echo-pref-email-frequency-immediately' => EchoEmailFrequency::IMMEDIATELY,
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2012-12-14 20:48:41 +00:00
|
|
|
// Only show digest options if email batch is enabled
|
|
|
|
if ( $wgEchoEnableEmailBatch ) {
|
2016-12-05 18:51:07 +00:00
|
|
|
$freqOptions += [
|
2018-12-31 15:54:19 +00:00
|
|
|
'echo-pref-email-frequency-daily' => EchoEmailFrequency::DAILY_DIGEST,
|
|
|
|
'echo-pref-email-frequency-weekly' => EchoEmailFrequency::WEEKLY_DIGEST,
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2012-12-14 20:48:41 +00:00
|
|
|
}
|
2016-12-05 18:51:07 +00:00
|
|
|
$preferences['echo-email-frequency'] = [
|
2012-11-16 21:03:57 +00:00
|
|
|
'type' => 'select',
|
2013-04-18 00:44:20 +00:00
|
|
|
'label-message' => 'echo-pref-send-me',
|
|
|
|
'section' => 'echo/emailsettings',
|
2018-12-31 15:54:19 +00:00
|
|
|
'options-messages' => $freqOptions
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2012-11-16 21:03:57 +00:00
|
|
|
|
2019-12-10 00:13:29 +00:00
|
|
|
$preferences['echo-dont-email-read-notifications'] = [
|
|
|
|
'type' => 'toggle',
|
|
|
|
'label-message' => 'echo-pref-dont-email-read-notifications',
|
|
|
|
'section' => 'echo/emailsettings',
|
|
|
|
'hide-if' => [ 'OR', [ '===', 'echo-email-frequency', '-1' ], [ '===', 'echo-email-frequency', '0' ] ]
|
|
|
|
];
|
|
|
|
|
2012-11-16 21:03:57 +00:00
|
|
|
// Display information about the user's currently set email address
|
|
|
|
$prefsTitle = SpecialPage::getTitleFor( 'Preferences', false, 'mw-prefsection-echo' );
|
2016-12-04 13:08:41 +00:00
|
|
|
$link = MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
|
2012-11-16 21:03:57 +00:00
|
|
|
SpecialPage::getTitleFor( 'ChangeEmail' ),
|
2016-12-04 13:08:41 +00:00
|
|
|
wfMessage( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
|
2016-12-05 18:51:07 +00:00
|
|
|
[],
|
|
|
|
[ 'returnto' => $prefsTitle->getFullText() ]
|
2012-11-16 21:03:57 +00:00
|
|
|
);
|
2020-03-05 19:55:54 +00:00
|
|
|
$permManager = MediaWikiServices::getInstance()->getPermissionManager();
|
|
|
|
$emailAddress = $user->getEmail() && $permManager->userHasRight( $user, 'viewmyprivateinfo' )
|
2016-05-12 17:18:58 +00:00
|
|
|
? htmlspecialchars( $user->getEmail() ) : '';
|
2020-03-05 19:55:54 +00:00
|
|
|
if ( $permManager->userHasRight( $user, 'editmyprivateinfo' ) && self::isEmailChangeAllowed() ) {
|
2012-11-16 21:03:57 +00:00
|
|
|
if ( $emailAddress === '' ) {
|
|
|
|
$emailAddress .= $link;
|
|
|
|
} else {
|
|
|
|
$emailAddress .= wfMessage( 'word-separator' )->escaped()
|
|
|
|
. wfMessage( 'parentheses' )->rawParams( $link )->escaped();
|
|
|
|
}
|
|
|
|
}
|
2016-12-05 18:51:07 +00:00
|
|
|
$preferences['echo-emailaddress'] = [
|
2012-11-16 21:03:57 +00:00
|
|
|
'type' => 'info',
|
|
|
|
'raw' => true,
|
2013-04-18 00:44:20 +00:00
|
|
|
'default' => $emailAddress,
|
|
|
|
'label-message' => 'echo-pref-send-to',
|
|
|
|
'section' => 'echo/emailsettings'
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2012-11-16 21:03:57 +00:00
|
|
|
|
2013-06-24 00:22:08 +00:00
|
|
|
// Only show this option if html email is allowed, otherwise it is always plain text format
|
|
|
|
if ( $wgAllowHTMLEmail ) {
|
|
|
|
// Email format
|
2016-12-05 18:51:07 +00:00
|
|
|
$preferences['echo-email-format'] = [
|
2013-06-24 00:22:08 +00:00
|
|
|
'type' => 'select',
|
|
|
|
'label-message' => 'echo-pref-email-format',
|
|
|
|
'section' => 'echo/emailsettings',
|
2018-12-31 15:54:19 +00:00
|
|
|
'options-messages' => [
|
|
|
|
'echo-pref-email-format-html' => EchoEmailFormat::HTML,
|
|
|
|
'echo-pref-email-format-plain-text' => EchoEmailFormat::PLAIN_TEXT,
|
2016-12-05 18:51:07 +00:00
|
|
|
]
|
|
|
|
];
|
2013-06-24 00:22:08 +00:00
|
|
|
}
|
|
|
|
|
2013-02-16 02:20:34 +00:00
|
|
|
// Sort notification categories by priority
|
2016-12-05 18:51:07 +00:00
|
|
|
$categoriesAndPriorities = [];
|
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-19 02:54:15 +00:00
|
|
|
foreach ( $attributeManager->getInternalCategoryNames() as $category ) {
|
|
|
|
// See if the category should be hidden from preferences.
|
|
|
|
if ( !$attributeManager->isCategoryDisplayedInPreferences( $category ) ) {
|
2013-02-16 02:20:34 +00:00
|
|
|
continue;
|
|
|
|
}
|
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-19 02:54:15 +00:00
|
|
|
|
2016-12-28 11:29:44 +00:00
|
|
|
// See if user is eligible to receive this notification (per user group restrictions)
|
2014-07-22 21:33:22 +00:00
|
|
|
if ( $attributeManager->getCategoryEligibility( $user, $category ) ) {
|
|
|
|
$categoriesAndPriorities[$category] = $attributeManager->getCategoryPriority( $category );
|
2013-01-14 23:52:46 +00:00
|
|
|
}
|
|
|
|
}
|
2013-02-16 02:20:34 +00:00
|
|
|
asort( $categoriesAndPriorities );
|
|
|
|
$validSortedCategories = array_keys( $categoriesAndPriorities );
|
2013-01-14 23:52:46 +00:00
|
|
|
|
2019-12-09 02:42:55 +00:00
|
|
|
// Show subscription options. IMPORTANT: 'echo-subscriptions-email-edit-user-talk',
|
|
|
|
// 'echo-subscriptions-email-watchlist', and 'echo-subscriptions-email-minor-watchlist' are
|
|
|
|
// virtual options, their values are saved to existing notification options 'enotifusertalkpages',
|
2022-01-09 19:16:10 +00:00
|
|
|
// 'enotifwatchlistpages', and 'enotifminoredits', see onLoadUserOptions() and onSaveUserOptions()
|
2019-12-09 02:42:55 +00:00
|
|
|
// for more information on how it is handled. Doing it in this way, we can avoid keeping running
|
2013-05-09 21:23:15 +00:00
|
|
|
// massive data migration script to keep these two options synced when echo is enabled on
|
|
|
|
// new wikis or Echo is disabled and re-enabled for some reason. We can update the name
|
|
|
|
// if Echo is ever merged to core
|
2013-02-16 02:20:34 +00:00
|
|
|
|
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-19 02:54:15 +00:00
|
|
|
// Build the columns (notify types)
|
2016-12-05 18:51:07 +00:00
|
|
|
$columns = [];
|
2013-02-16 02:20:34 +00:00
|
|
|
foreach ( $wgEchoNotifiers as $notifierType => $notifierData ) {
|
2013-04-16 22:40:45 +00:00
|
|
|
$formatMessage = wfMessage( 'echo-pref-' . $notifierType )->escaped();
|
2013-02-16 02:20:34 +00:00
|
|
|
$columns[$formatMessage] = $notifierType;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the rows (notification categories)
|
2016-12-05 18:51:07 +00:00
|
|
|
$rows = [];
|
|
|
|
$tooltips = [];
|
2013-02-16 02:20:34 +00:00
|
|
|
foreach ( $validSortedCategories as $category ) {
|
2013-04-16 22:40:45 +00:00
|
|
|
$categoryMessage = wfMessage( 'echo-category-title-' . $category )->numParams( 1 )->escaped();
|
2013-02-16 02:20:34 +00:00
|
|
|
$rows[$categoryMessage] = $category;
|
2013-05-02 00:12:59 +00:00
|
|
|
if ( isset( $wgEchoNotificationCategories[$category]['tooltip'] ) ) {
|
2016-04-02 13:41:56 +00:00
|
|
|
$tooltips[$categoryMessage] = wfMessage( $wgEchoNotificationCategories[$category]['tooltip'] )->text();
|
2013-05-02 00:12:59 +00:00
|
|
|
}
|
2013-02-16 02:20:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Figure out the individual exceptions in the matrix and make them disabled
|
2016-12-05 18:51:07 +00:00
|
|
|
$forceOptionsOff = $forceOptionsOn = [];
|
2013-02-16 02:20:34 +00:00
|
|
|
foreach ( $wgEchoNotifiers as $notifierType => $notifierData ) {
|
|
|
|
foreach ( $validSortedCategories as $category ) {
|
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-19 02:54:15 +00:00
|
|
|
// See if this notify type is non-dismissable
|
|
|
|
if ( !$attributeManager->isNotifyTypeDismissableForCategory( $category, $notifierType ) ) {
|
2013-04-29 17:58:05 +00:00
|
|
|
$forceOptionsOn[] = "$notifierType-$category";
|
2013-01-14 23:52:46 +00:00
|
|
|
}
|
2013-02-16 02:20:34 +00:00
|
|
|
|
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-19 02:54:15 +00:00
|
|
|
if ( !$attributeManager->isNotifyTypeAvailableForCategory( $category, $notifierType ) ) {
|
2013-04-29 17:58:05 +00:00
|
|
|
$forceOptionsOff[] = "$notifierType-$category";
|
2013-01-14 23:52:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-29 17:58:05 +00:00
|
|
|
$invalid = array_intersect( $forceOptionsOff, $forceOptionsOn );
|
|
|
|
if ( $invalid ) {
|
|
|
|
throw new MWException( sprintf(
|
|
|
|
'The following notifications are both forced and removed: %s',
|
|
|
|
implode( ', ', $invalid )
|
|
|
|
) );
|
|
|
|
}
|
2016-12-05 18:51:07 +00:00
|
|
|
$preferences['echo-subscriptions'] = [
|
2021-05-07 22:03:13 +00:00
|
|
|
'class' => HTMLCheckMatrix::class,
|
2013-02-16 02:20:34 +00:00
|
|
|
'section' => 'echo/echosubscriptions',
|
|
|
|
'rows' => $rows,
|
|
|
|
'columns' => $columns,
|
2013-05-22 19:50:24 +00:00
|
|
|
'prefix' => 'echo-subscriptions-',
|
2013-04-29 17:58:05 +00:00
|
|
|
'force-options-off' => $forceOptionsOff,
|
|
|
|
'force-options-on' => $forceOptionsOn,
|
2013-05-02 00:12:59 +00:00
|
|
|
'tooltips' => $tooltips,
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2013-03-20 01:10:23 +00:00
|
|
|
|
2019-04-18 05:18:13 +00:00
|
|
|
if ( $wgEchoCrossWikiNotifications ) {
|
2016-12-05 18:51:07 +00:00
|
|
|
$preferences['echo-cross-wiki-notifications'] = [
|
2016-03-11 01:48:06 +00:00
|
|
|
'type' => 'toggle',
|
|
|
|
'label-message' => 'echo-pref-cross-wiki-notifications',
|
|
|
|
'section' => 'echo/echocrosswiki'
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2016-03-11 01:48:06 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 20:14:05 +00:00
|
|
|
if ( $wgEchoPollForUpdates ) {
|
|
|
|
$preferences['echo-show-poll-updates'] = [
|
|
|
|
'type' => 'toggle',
|
|
|
|
'label-message' => 'echo-pref-show-poll-updates',
|
|
|
|
'help-message' => 'echo-pref-show-poll-updates-help',
|
|
|
|
'section' => 'echo/echopollupdates'
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2019-12-09 02:42:55 +00:00
|
|
|
// If we're using Echo to handle user talk page post or watchlist notifications,
|
|
|
|
// hide the old (non-Echo) preferences for them. If Echo is moved to core
|
|
|
|
// we'll want to remove the old user options entirely. For now, though,
|
2013-04-28 21:55:05 +00:00
|
|
|
// we need to keep it defined in case Echo is ever uninstalled.
|
|
|
|
// Otherwise, that preference could be lost entirely. This hiding logic
|
2019-12-09 02:42:55 +00:00
|
|
|
// is not abstracted since there are only three preferences in core
|
|
|
|
// that are potentially made obsolete by Echo.
|
2013-04-28 21:55:05 +00:00
|
|
|
if ( isset( $wgEchoNotifications['edit-user-talk'] ) ) {
|
|
|
|
$preferences['enotifusertalkpages']['type'] = 'hidden';
|
|
|
|
unset( $preferences['enotifusertalkpages']['section'] );
|
|
|
|
}
|
2019-12-09 02:42:55 +00:00
|
|
|
if ( $wgEchoWatchlistNotifications && isset( $wgEchoNotifications['watchlist-change'] ) ) {
|
|
|
|
$preferences['enotifwatchlistpages']['type'] = 'hidden';
|
|
|
|
unset( $preferences['enotifusertalkpages']['section'] );
|
|
|
|
$preferences['enotifminoredits']['type'] = 'hidden';
|
|
|
|
unset( $preferences['enotifminoredits']['section'] );
|
|
|
|
}
|
2013-04-28 21:55:05 +00:00
|
|
|
|
2016-11-10 04:37:10 +00:00
|
|
|
if ( $wgEchoPerUserBlacklist ) {
|
|
|
|
$preferences['echo-notifications-blacklist'] = [
|
2017-05-20 13:51:30 +00:00
|
|
|
'type' => 'usersmultiselect',
|
2016-11-10 04:37:10 +00:00
|
|
|
'label-message' => 'echo-pref-notifications-blacklist',
|
|
|
|
'section' => 'echo/blocknotificationslist',
|
2018-07-09 22:45:54 +00:00
|
|
|
'filter' => MultiUsernameFilter::class,
|
2016-11-10 04:37:10 +00:00
|
|
|
];
|
2020-04-20 19:40:07 +00:00
|
|
|
$preferences['echo-notifications-page-linked-title-muted-list'] = [
|
|
|
|
'type' => 'titlesmultiselect',
|
|
|
|
'label-message' => 'echo-pref-notifications-page-linked-title-muted-list',
|
|
|
|
'section' => 'echo/mutedpageslist',
|
2020-06-03 10:49:26 +00:00
|
|
|
'showMissing' => false,
|
2022-01-09 19:16:10 +00:00
|
|
|
'excludeDynamicNamespaces' => true,
|
|
|
|
'filter' => new MultiTitleFilter()
|
2020-04-20 19:40:07 +00:00
|
|
|
];
|
2016-11-10 04:37:10 +00:00
|
|
|
}
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
2022-10-12 00:50:11 +00:00
|
|
|
/**
|
|
|
|
* Add icon for Special:Preferences mobile layout
|
|
|
|
*
|
|
|
|
* @param array &$iconNames Array of icon names for their respective sections.
|
|
|
|
*/
|
|
|
|
public function onPreferencesGetIcon( &$iconNames ) {
|
|
|
|
$iconNames[ 'echo' ] = 'bell';
|
|
|
|
}
|
|
|
|
|
2016-05-25 19:03:29 +00:00
|
|
|
/**
|
|
|
|
* Test whether email address change is supposed to be allowed
|
2017-08-09 15:20:55 +00:00
|
|
|
* @return bool
|
2016-05-25 19:03:29 +00:00
|
|
|
*/
|
|
|
|
private static function isEmailChangeAllowed() {
|
2020-04-04 14:59:12 +00:00
|
|
|
return MediaWikiServices::getInstance()->getAuthManager()
|
|
|
|
->allowsPropertyChange( 'emailaddress' );
|
2016-05-25 19:03:29 +00:00
|
|
|
}
|
|
|
|
|
2012-05-17 00:29:37 +00:00
|
|
|
/**
|
2020-06-16 04:03:45 +00:00
|
|
|
* Handler for PageSaveComplete hook
|
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/PageSaveComplete
|
2017-12-08 13:49:55 +00:00
|
|
|
*
|
2019-04-17 15:46:06 +00:00
|
|
|
* @param WikiPage $wikiPage modified WikiPage
|
2020-06-16 04:03:45 +00:00
|
|
|
* @param UserIdentity $userIdentity User who edited
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param string $summary Edit summary
|
2020-06-16 04:03:45 +00:00
|
|
|
* @param int $flags Edit flags
|
|
|
|
* @param RevisionRecord $revisionRecord RevisionRecord for the revision that was created
|
|
|
|
* @param EditResult $editResult
|
2012-05-17 00:29:37 +00:00
|
|
|
*/
|
2022-08-22 22:36:48 +00:00
|
|
|
public function onPageSaveComplete(
|
|
|
|
$wikiPage,
|
|
|
|
$userIdentity,
|
|
|
|
$summary,
|
|
|
|
$flags,
|
|
|
|
$revisionRecord,
|
|
|
|
$editResult
|
2018-08-25 10:51:14 +00:00
|
|
|
) {
|
2020-06-16 04:03:45 +00:00
|
|
|
if ( $editResult->isNullEdit() ) {
|
2019-12-27 11:11:11 +00:00
|
|
|
return;
|
2016-02-24 08:27:17 +00:00
|
|
|
}
|
|
|
|
|
2017-12-08 13:49:55 +00:00
|
|
|
$title = $wikiPage->getTitle();
|
2021-03-17 11:07:45 +00:00
|
|
|
$isRevert = $editResult->getRevertMethod() === EditResult::REVERT_UNDO ||
|
|
|
|
$editResult->getRevertMethod() === EditResult::REVERT_ROLLBACK;
|
2012-07-27 22:16:19 +00:00
|
|
|
|
2021-12-08 05:19:00 +00:00
|
|
|
// Save the revert status for the LinksUpdateComplete hook
|
|
|
|
if ( $isRevert ) {
|
|
|
|
self::$revertedRevIds[$revisionRecord->getId()] = true;
|
|
|
|
}
|
|
|
|
|
2015-05-18 19:58:15 +00:00
|
|
|
// Try to do this after the HTTP response
|
2021-03-17 11:07:45 +00:00
|
|
|
DeferredUpdates::addCallableUpdate( static function () use ( $revisionRecord, $isRevert ) {
|
2020-06-16 04:03:45 +00:00
|
|
|
EchoDiscussionParser::generateEventsForRevision( $revisionRecord, $isRevert );
|
2015-05-18 19:58:15 +00:00
|
|
|
} );
|
|
|
|
|
2016-01-18 23:55:47 +00:00
|
|
|
// If the user is not an IP and this is not a null edit,
|
|
|
|
// test for them reaching a congratulatory threshold
|
2020-06-17 18:39:39 +00:00
|
|
|
$thresholds = [ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 ];
|
2021-12-19 01:16:52 +00:00
|
|
|
if ( $userIdentity->isRegistered() ) {
|
|
|
|
$thresholdCount = self::getEditCount( $userIdentity );
|
2016-03-09 01:05:05 +00:00
|
|
|
if ( in_array( $thresholdCount, $thresholds ) ) {
|
2021-12-19 01:16:52 +00:00
|
|
|
DeferredUpdates::addCallableUpdate( static function () use ( $userIdentity, $title, $thresholdCount ) {
|
2022-11-02 20:47:04 +00:00
|
|
|
$notificationMapper = new NotificationMapper();
|
2021-12-19 01:16:52 +00:00
|
|
|
$notifications = $notificationMapper->fetchByUser( $userIdentity, 10, null, [ 'thank-you-edit' ] );
|
2016-08-03 15:41:50 +00:00
|
|
|
/** @var EchoNotification $notification */
|
|
|
|
foreach ( $notifications as $notification ) {
|
|
|
|
if ( $notification->getEvent()->getExtraParam( 'editCount' ) === $thresholdCount ) {
|
|
|
|
LoggerFactory::getInstance( 'Echo' )->debug(
|
|
|
|
'{user} (id: {id}) has already been thanked for their {count} edit',
|
2016-12-05 18:51:07 +00:00
|
|
|
[
|
2021-12-19 01:16:52 +00:00
|
|
|
'user' => $userIdentity->getName(),
|
|
|
|
'id' => $userIdentity->getId(),
|
2016-08-03 15:41:50 +00:00
|
|
|
'count' => $thresholdCount,
|
2016-12-05 18:51:07 +00:00
|
|
|
]
|
2016-08-03 15:41:50 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
2016-03-04 05:48:01 +00:00
|
|
|
}
|
2016-08-03 15:41:50 +00:00
|
|
|
|
2016-12-05 18:51:07 +00:00
|
|
|
EchoEvent::create( [
|
2022-06-23 09:17:10 +00:00
|
|
|
'type' => 'thank-you-edit',
|
|
|
|
'title' => $title,
|
|
|
|
'agent' => $userIdentity,
|
|
|
|
// Edit threshold notifications are sent to the agent
|
|
|
|
'extra' => [
|
|
|
|
'editCount' => $thresholdCount,
|
2016-12-05 18:51:07 +00:00
|
|
|
]
|
2022-06-23 09:17:10 +00:00
|
|
|
] );
|
2016-01-18 23:55:47 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-18 19:58:15 +00:00
|
|
|
// Handle the case of someone undoing an edit, either through the
|
|
|
|
// 'undo' link in the article history or via the API.
|
2021-03-17 11:07:45 +00:00
|
|
|
// Reverts through the 'rollback' link (EditResult::REVERT_ROLLBACK)
|
|
|
|
// are handled in ::onRollbackComplete().
|
|
|
|
if ( $editResult->getRevertMethod() === EditResult::REVERT_UNDO ) {
|
2019-04-17 15:46:06 +00:00
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2021-03-17 11:07:45 +00:00
|
|
|
$undidRevId = $editResult->getUndidRevId();
|
2019-04-17 15:46:06 +00:00
|
|
|
$undidRevision = $store->getRevisionById( $undidRevId );
|
|
|
|
if (
|
|
|
|
$undidRevision &&
|
|
|
|
Title::newFromLinkTarget( $undidRevision->getPageAsLinkTarget() )->equals( $title )
|
|
|
|
) {
|
2020-07-21 11:15:09 +00:00
|
|
|
$revertedUser = $undidRevision->getUser();
|
2020-07-21 11:18:29 +00:00
|
|
|
// No notifications for anonymous users
|
|
|
|
if ( $revertedUser && $revertedUser->getId() ) {
|
2016-12-18 02:21:53 +00:00
|
|
|
EchoEvent::create( [
|
|
|
|
'type' => 'reverted',
|
|
|
|
'title' => $title,
|
|
|
|
'extra' => [
|
2020-06-16 04:03:45 +00:00
|
|
|
'revid' => $revisionRecord->getId(),
|
2020-07-21 11:18:29 +00:00
|
|
|
'reverted-user-id' => $revertedUser->getId(),
|
2016-12-18 02:21:53 +00:00
|
|
|
'reverted-revision-id' => $undidRevId,
|
2018-03-26 18:54:03 +00:00
|
|
|
'method' => 'undo',
|
2016-12-18 02:21:53 +00:00
|
|
|
'summary' => $summary,
|
|
|
|
],
|
2021-12-19 01:16:52 +00:00
|
|
|
'agent' => $userIdentity,
|
2016-12-18 02:21:53 +00:00
|
|
|
] );
|
2012-07-18 19:39:33 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-01 20:15:37 +00:00
|
|
|
}
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
2012-06-01 10:57:09 +00:00
|
|
|
|
2019-03-04 15:49:19 +00:00
|
|
|
/**
|
2021-12-19 01:16:52 +00:00
|
|
|
* @param UserIdentity $user
|
2019-03-04 15:49:19 +00:00
|
|
|
* @return int
|
|
|
|
*/
|
2021-12-19 01:16:52 +00:00
|
|
|
private static function getEditCount( UserIdentity $user ) {
|
|
|
|
$editCount = MediaWikiServices::getInstance()->getUserEditTracker()
|
|
|
|
->getUserEditCount( $user ) ?: 0;
|
2019-03-04 15:49:19 +00:00
|
|
|
// When this code runs from a maintenance script or unit tests
|
|
|
|
// the deferred update incrementing edit count runs right away
|
|
|
|
// so the edit count is right. Otherwise it lags by one.
|
|
|
|
if ( wfIsCLI() ) {
|
2021-12-19 01:16:52 +00:00
|
|
|
return $editCount;
|
2019-03-04 15:49:19 +00:00
|
|
|
}
|
2021-12-19 01:16:52 +00:00
|
|
|
return $editCount + 1;
|
2019-03-04 15:49:19 +00:00
|
|
|
}
|
|
|
|
|
2013-05-01 19:27:32 +00:00
|
|
|
/**
|
|
|
|
* Handler for EchoAbortEmailNotification hook
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param User $user
|
|
|
|
* @param EchoEvent $event
|
2013-05-01 19:27:32 +00:00
|
|
|
* @return bool true - send email, false - do not send email
|
|
|
|
*/
|
|
|
|
public static function onEchoAbortEmailNotification( $user, $event ) {
|
2019-12-09 02:42:55 +00:00
|
|
|
global $wgEchoWatchlistEmailOncePerPage;
|
|
|
|
$type = $event->getType();
|
|
|
|
if ( $type === 'edit-user-talk' ) {
|
2013-05-01 19:27:32 +00:00
|
|
|
$extra = $event->getExtra();
|
|
|
|
if ( !empty( $extra['minoredit'] ) ) {
|
|
|
|
global $wgEnotifMinorEdits;
|
2021-12-01 18:29:00 +00:00
|
|
|
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
|
|
|
|
if ( !$wgEnotifMinorEdits || !$userOptionsLookup->getOption( $user, 'enotifminoredits' ) ) {
|
2013-05-01 19:27:32 +00:00
|
|
|
// Do not send talk page notification email
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2019-12-09 02:42:55 +00:00
|
|
|
// Mimic core code of only sending watchlist notification emails once per page
|
|
|
|
} elseif ( $type === "watchlist-change" || $type === "minor-watchlist-change" ) {
|
|
|
|
if ( !$wgEchoWatchlistEmailOncePerPage ) {
|
|
|
|
// Don't care about rate limiting
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
$store = MediaWikiServices::getInstance()->getWatchedItemStore();
|
|
|
|
$ts = $store->getWatchedItem( $user, $event->getTitle() )->getNotificationTimestamp();
|
|
|
|
// if (ts != null) is not sufficient because, if $wgEchoUseJobQueue is set,
|
|
|
|
// wl_notificationtimestamp will have already been set for the new edit
|
|
|
|
// by the time this code runs.
|
|
|
|
if ( $ts !== null && $ts !== $event->getExtraParam( "timestamp" ) ) {
|
|
|
|
// User has already seen an email for this page before
|
|
|
|
return false;
|
|
|
|
}
|
2013-05-01 19:27:32 +00:00
|
|
|
}
|
2019-12-09 02:42:55 +00:00
|
|
|
// Proceed to send notification email
|
2013-05-01 19:27:32 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
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-19 02:54:15 +00:00
|
|
|
/**
|
|
|
|
* Get overrides for new users. This allows changes that only apply going forward,
|
|
|
|
* without affecting existing users.
|
|
|
|
*
|
2018-08-13 07:25:22 +00:00
|
|
|
* @return bool[] Associative array mapping key to bool for whether it should be enabled
|
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-19 02:54:15 +00:00
|
|
|
*/
|
|
|
|
public static function getNewUserPreferenceOverrides() {
|
2016-12-05 18:51:07 +00:00
|
|
|
return [
|
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-19 02:54:15 +00:00
|
|
|
'echo-subscriptions-web-reverted' => false,
|
|
|
|
'echo-subscriptions-email-reverted' => false,
|
|
|
|
'echo-subscriptions-web-article-linked' => true,
|
|
|
|
'echo-subscriptions-email-mention' => true,
|
|
|
|
'echo-subscriptions-email-article-linked' => true,
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
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-19 02:54:15 +00:00
|
|
|
}
|
|
|
|
|
2012-08-31 23:35:16 +00:00
|
|
|
/**
|
2016-05-25 19:03:29 +00:00
|
|
|
* Handler for LocalUserCreated hook.
|
2018-10-23 20:35:09 +00:00
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/LocalUserCreated
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param User $user User object that was created.
|
|
|
|
* @param bool $autocreated True when account was auto-created
|
2012-08-31 23:35:16 +00:00
|
|
|
*/
|
2022-08-22 22:46:42 +00:00
|
|
|
public function onLocalUserCreated( $user, $autocreated ) {
|
2016-05-25 19:03:29 +00:00
|
|
|
if ( !$autocreated ) {
|
|
|
|
$overrides = self::getNewUserPreferenceOverrides();
|
2021-03-30 22:14:16 +00:00
|
|
|
$userOptionsManager = MediaWikiServices::getInstance()->getUserOptionsManager();
|
2016-05-25 19:03:29 +00:00
|
|
|
foreach ( $overrides as $prefKey => $value ) {
|
2021-03-30 22:14:16 +00:00
|
|
|
$userOptionsManager->setOption( $user, $prefKey, $value );
|
2016-05-25 19:03:29 +00:00
|
|
|
}
|
2016-12-05 18:51:07 +00:00
|
|
|
EchoEvent::create( [
|
2016-05-25 19:03:29 +00:00
|
|
|
'type' => 'welcome',
|
|
|
|
'agent' => $user,
|
2016-12-05 18:51:07 +00:00
|
|
|
] );
|
2016-05-25 19:03:29 +00:00
|
|
|
}
|
2012-08-31 23:35:16 +00:00
|
|
|
|
2016-09-09 21:46:11 +00:00
|
|
|
$seenTime = EchoSeenTime::newFromUser( $user );
|
|
|
|
|
|
|
|
// Set seen time to UNIX epoch, so initially all notifications are unseen.
|
|
|
|
$seenTime->setTime( wfTimestamp( TS_MW, 1 ), 'all' );
|
2012-08-31 23:35:16 +00:00
|
|
|
}
|
|
|
|
|
2013-03-13 00:49:19 +00:00
|
|
|
/**
|
2015-09-22 18:16:59 +00:00
|
|
|
* Handler for UserGroupsChanged hook.
|
2018-10-23 20:35:09 +00:00
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/UserGroupsChanged
|
2014-05-27 18:28:37 +00:00
|
|
|
*
|
2022-05-01 15:16:32 +00:00
|
|
|
* @param UserIdentity $userId user that was changed
|
2015-09-22 18:16:59 +00:00
|
|
|
* @param string[] $add strings corresponding to groups added
|
|
|
|
* @param string[] $remove strings corresponding to groups removed
|
|
|
|
* @param User|bool $performer
|
2016-02-08 20:45:44 +00:00
|
|
|
* @param string|bool $reason Reason given by the user changing the rights
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param array $oldUGMs
|
|
|
|
* @param array $newUGMs
|
2013-03-13 00:49:19 +00:00
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public function onUserGroupsChanged( $userId, $add, $remove, $performer,
|
|
|
|
$reason, $oldUGMs, $newUGMs ) {
|
2015-09-22 18:16:59 +00:00
|
|
|
if ( !$performer ) {
|
|
|
|
// TODO: Implement support for autopromotion
|
2019-12-27 11:11:11 +00:00
|
|
|
return;
|
2015-09-22 18:16:59 +00:00
|
|
|
}
|
|
|
|
|
2022-05-01 15:16:32 +00:00
|
|
|
if ( $userId->getWikiId() !== WikiAwareEntity::LOCAL ) {
|
|
|
|
// TODO: Support external users
|
2019-12-27 11:11:11 +00:00
|
|
|
return;
|
2015-09-22 18:16:59 +00:00
|
|
|
}
|
|
|
|
|
2022-05-01 15:16:32 +00:00
|
|
|
$user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $userId );
|
|
|
|
|
2015-09-22 18:16:59 +00:00
|
|
|
if ( $user->equals( $performer ) ) {
|
|
|
|
// Don't notify for self changes
|
2019-12-27 11:11:11 +00:00
|
|
|
return;
|
2015-09-22 18:16:59 +00:00
|
|
|
}
|
2013-03-13 00:49:19 +00:00
|
|
|
|
2017-04-08 07:43:05 +00:00
|
|
|
// If any old groups are in $add, those groups are having their expiry
|
|
|
|
// changed, not actually being added
|
|
|
|
$expiryChanged = [];
|
|
|
|
$reallyAdded = [];
|
|
|
|
foreach ( $add as $group ) {
|
|
|
|
if ( isset( $oldUGMs[$group] ) ) {
|
|
|
|
$expiryChanged[] = $group;
|
|
|
|
} else {
|
|
|
|
$reallyAdded[] = $group;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $expiryChanged ) {
|
|
|
|
// use a separate notification for these, so the notification text doesn't
|
|
|
|
// get too long
|
|
|
|
EchoEvent::create(
|
|
|
|
[
|
|
|
|
'type' => 'user-rights',
|
|
|
|
'extra' => [
|
2019-06-11 21:43:05 +00:00
|
|
|
'user' => $user->getId(),
|
2017-04-08 07:43:05 +00:00
|
|
|
'expiry-changed' => $expiryChanged,
|
|
|
|
'reason' => $reason,
|
|
|
|
],
|
|
|
|
'agent' => $performer,
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $reallyAdded || $remove ) {
|
2013-03-13 00:49:19 +00:00
|
|
|
EchoEvent::create(
|
2016-12-05 18:51:07 +00:00
|
|
|
[
|
2013-03-13 00:49:19 +00:00
|
|
|
'type' => 'user-rights',
|
2016-12-05 18:51:07 +00:00
|
|
|
'extra' => [
|
2019-06-11 21:43:05 +00:00
|
|
|
'user' => $user->getId(),
|
2017-04-08 07:43:05 +00:00
|
|
|
'add' => $reallyAdded,
|
2016-02-08 20:45:44 +00:00
|
|
|
'remove' => $remove,
|
|
|
|
'reason' => $reason,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
2015-09-22 18:16:59 +00:00
|
|
|
'agent' => $performer,
|
2016-12-05 18:51:07 +00:00
|
|
|
]
|
2013-03-13 00:49:19 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-26 22:05:29 +00:00
|
|
|
/**
|
2021-12-08 05:19:00 +00:00
|
|
|
* Handler for LinksUpdateComplete hook.
|
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/LinksUpdateComplete
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param LinksUpdate $linksUpdate
|
2021-12-08 05:19:00 +00:00
|
|
|
* @param mixed $ticket
|
2012-12-26 22:05:29 +00:00
|
|
|
*/
|
2022-08-22 22:46:42 +00:00
|
|
|
public function onLinksUpdateComplete( $linksUpdate, $ticket ) {
|
2013-03-18 21:48:33 +00:00
|
|
|
// Rollback or undo should not trigger link notification
|
2021-12-08 05:19:00 +00:00
|
|
|
if ( $linksUpdate->getRevisionRecord() ) {
|
|
|
|
$revId = $linksUpdate->getRevisionRecord()->getId();
|
|
|
|
if ( isset( self::$revertedRevIds[$revId] ) ) {
|
|
|
|
return;
|
|
|
|
}
|
2013-03-18 21:48:33 +00:00
|
|
|
}
|
|
|
|
|
2021-09-26 14:40:27 +00:00
|
|
|
$namespaceInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
|
|
|
|
|
2013-01-16 00:13:58 +00:00
|
|
|
// Handle only
|
2021-12-08 05:19:00 +00:00
|
|
|
// 1. content namespace pages &&
|
|
|
|
// 2. non-transcluding pages &&
|
|
|
|
// 3. non-redirect pages
|
|
|
|
if ( !$namespaceInfo->isContent( $linksUpdate->getTitle()->getNamespace() )
|
2021-12-08 04:00:37 +00:00
|
|
|
|| !$linksUpdate->isRecursive() || $linksUpdate->getTitle()->isRedirect()
|
2015-10-01 13:48:52 +00:00
|
|
|
) {
|
2019-12-27 11:11:11 +00:00
|
|
|
return;
|
2013-01-04 01:36:12 +00:00
|
|
|
}
|
|
|
|
|
2020-04-08 02:31:12 +00:00
|
|
|
$revRecord = $linksUpdate->getRevisionRecord();
|
|
|
|
$revid = $revRecord ? $revRecord->getId() : null;
|
|
|
|
$user = $revRecord ? $revRecord->getUser() : null;
|
2016-05-23 19:22:41 +00:00
|
|
|
|
2013-01-15 23:21:39 +00:00
|
|
|
// link notification is boundless as you can include infinite number of links in a page
|
|
|
|
// db insert is expensive, limit it to a reasonable amount, we can increase this limit
|
|
|
|
// once the storage is on Redis
|
|
|
|
$max = 10;
|
2013-01-08 23:57:28 +00:00
|
|
|
// Only create notifications for links to content namespace pages
|
2013-01-15 23:21:39 +00:00
|
|
|
// @Todo - use one big insert instead of individual insert inside foreach loop
|
2021-12-08 05:19:00 +00:00
|
|
|
foreach ( $linksUpdate->getAddedLinks() as $title ) {
|
|
|
|
if ( $namespaceInfo->isContent( $title->getNamespace() ) ) {
|
2013-05-03 16:30:29 +00:00
|
|
|
if ( $title->isRedirect() ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-12-08 04:00:37 +00:00
|
|
|
$linkFromPageId = $linksUpdate->getTitle()->getArticleID();
|
2016-12-05 18:51:07 +00:00
|
|
|
EchoEvent::create( [
|
2013-01-15 23:21:39 +00:00
|
|
|
'type' => 'page-linked',
|
|
|
|
'title' => $title,
|
2015-10-27 23:17:25 +00:00
|
|
|
'agent' => $user,
|
2016-12-05 18:51:07 +00:00
|
|
|
'extra' => [
|
2016-07-27 14:52:18 +00:00
|
|
|
'target-page' => $linkFromPageId,
|
|
|
|
'link-from-page-id' => $linkFromPageId,
|
2016-05-23 19:22:41 +00:00
|
|
|
'revid' => $revid,
|
2016-12-05 18:51:07 +00:00
|
|
|
]
|
|
|
|
] );
|
2013-01-15 23:21:39 +00:00
|
|
|
$max--;
|
|
|
|
}
|
|
|
|
if ( $max < 0 ) {
|
|
|
|
break;
|
2013-01-04 01:36:12 +00:00
|
|
|
}
|
|
|
|
}
|
2012-12-26 22:05:29 +00:00
|
|
|
}
|
|
|
|
|
2012-05-17 00:29:37 +00:00
|
|
|
/**
|
|
|
|
* Handler for BeforePageDisplay hook.
|
2018-10-23 20:35:09 +00:00
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay
|
2018-06-17 16:56:02 +00:00
|
|
|
* @param OutputPage $out
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param Skin $skin Skin being used.
|
2012-05-17 00:29:37 +00:00
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public function onBeforePageDisplay( $out, $skin ): void {
|
2020-08-07 23:41:30 +00:00
|
|
|
$user = $out->getUser();
|
|
|
|
|
2020-12-18 02:32:42 +00:00
|
|
|
if ( !$user->isRegistered() ) {
|
2020-08-07 23:41:30 +00:00
|
|
|
return;
|
2012-06-01 10:57:09 +00:00
|
|
|
}
|
2020-08-07 23:41:30 +00:00
|
|
|
|
|
|
|
if ( self::shouldDisplayTalkAlert( $user, $out->getTitle() ) ) {
|
|
|
|
// Load the module for the Orange alert
|
|
|
|
$out->addModuleStyles( 'ext.echo.styles.alert' );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the module for the Notifications flyout
|
|
|
|
$out->addModules( [ 'ext.echo.init' ] );
|
|
|
|
// Load the styles for the Notifications badge
|
|
|
|
$out->addModuleStyles( [
|
|
|
|
'ext.echo.styles.badge',
|
|
|
|
'oojs-ui.styles.icons-alerts'
|
|
|
|
] );
|
2012-06-01 10:57:09 +00:00
|
|
|
}
|
|
|
|
|
2018-09-03 17:01:06 +00:00
|
|
|
private static function processMarkAsRead( User $user, WebRequest $request, Title $title ) {
|
|
|
|
global $wgEchoCrossWikiNotifications;
|
|
|
|
$subtractions = [
|
|
|
|
EchoAttributeManager::ALERT => 0,
|
|
|
|
EchoAttributeManager::MESSAGE => 0
|
|
|
|
];
|
2012-06-01 10:57:09 +00:00
|
|
|
|
2016-03-09 19:01:42 +00:00
|
|
|
// Attempt to mark a notification as read when visiting a page
|
2016-12-05 18:51:07 +00:00
|
|
|
$eventIds = [];
|
2014-08-07 00:07:34 +00:00
|
|
|
if ( $title->getArticleID() ) {
|
2022-11-02 20:47:04 +00:00
|
|
|
$eventMapper = new EventMapper();
|
2016-03-04 19:23:02 +00:00
|
|
|
$events = $eventMapper->fetchUnreadByUserAndPage( $user, $title->getArticleID() );
|
|
|
|
|
2018-06-17 17:00:05 +00:00
|
|
|
foreach ( $events as $event ) {
|
2018-09-03 17:01:06 +00:00
|
|
|
$subtractions[$event->getSection()]++;
|
2018-06-17 17:00:05 +00:00
|
|
|
$eventIds[] = $event->getId();
|
2014-08-07 00:07:34 +00:00
|
|
|
}
|
|
|
|
}
|
2016-06-13 20:41:12 +00:00
|
|
|
|
|
|
|
// Attempt to mark as read the event IDs in the ?markasread= parameter, if present
|
2021-09-18 23:39:22 +00:00
|
|
|
$markAsReadIds = array_filter( explode( '|', $request->getText( 'markasread' ) ) );
|
2021-12-21 00:47:31 +00:00
|
|
|
$markAsReadWiki = $request->getText( 'markasreadwiki', WikiMap::getCurrentWikiId() );
|
|
|
|
$markAsReadLocal = !$wgEchoCrossWikiNotifications || $markAsReadWiki === WikiMap::getCurrentWikiId();
|
2016-06-13 20:41:12 +00:00
|
|
|
if ( $markAsReadIds ) {
|
2018-09-03 17:01:06 +00:00
|
|
|
if ( $markAsReadLocal ) {
|
|
|
|
// gather the IDs that we didn't already find with target_pages
|
|
|
|
$eventsToMarkAsRead = [];
|
|
|
|
foreach ( $markAsReadIds as $markAsReadId ) {
|
|
|
|
$markAsReadId = intval( $markAsReadId );
|
|
|
|
if ( $markAsReadId !== 0 && !in_array( $markAsReadId, $eventIds ) ) {
|
|
|
|
$eventsToMarkAsRead[] = $markAsReadId;
|
|
|
|
}
|
2016-06-13 20:41:12 +00:00
|
|
|
}
|
|
|
|
|
2018-09-03 17:01:06 +00:00
|
|
|
if ( $eventsToMarkAsRead ) {
|
|
|
|
// fetch the notifications to adjust the counters
|
2022-11-02 20:47:04 +00:00
|
|
|
$notifMapper = new NotificationMapper();
|
2018-09-03 17:01:06 +00:00
|
|
|
$notifs = $notifMapper->fetchByUserEvents( $user, $eventsToMarkAsRead );
|
|
|
|
|
|
|
|
foreach ( $notifs as $notif ) {
|
|
|
|
if ( !$notif->getReadTimestamp() ) {
|
|
|
|
$subtractions[$notif->getEvent()->getSection()]++;
|
|
|
|
$eventIds[] = intval( $notif->getEvent()->getId() );
|
2016-06-13 20:41:12 +00:00
|
|
|
}
|
|
|
|
}
|
2016-05-03 01:56:13 +00:00
|
|
|
}
|
2018-09-03 17:01:06 +00:00
|
|
|
} else {
|
2019-12-21 05:45:14 +00:00
|
|
|
$markAsReadIds = array_map( 'intval', $markAsReadIds );
|
2018-09-03 17:01:06 +00:00
|
|
|
// Look up the notifications on the foreign wiki
|
|
|
|
$notifUser = MWEchoNotifUser::newFromUser( $user );
|
2021-10-21 11:19:39 +00:00
|
|
|
$notifInfo = $notifUser->getForeignNotificationInfo( $markAsReadIds, $markAsReadWiki, $request );
|
2018-09-03 17:01:06 +00:00
|
|
|
foreach ( $notifInfo as $id => $info ) {
|
|
|
|
$subtractions[$info['section']]++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Schedule a deferred update to mark these notifications as read on the foreign wiki
|
2021-10-21 11:19:39 +00:00
|
|
|
DeferredUpdates::addCallableUpdate(
|
|
|
|
static function () use ( $user, $markAsReadIds, $markAsReadWiki, $request ) {
|
|
|
|
$notifUser = MWEchoNotifUser::newFromUser( $user );
|
|
|
|
$notifUser->markReadForeign( $markAsReadIds, $markAsReadWiki, $request );
|
|
|
|
}
|
|
|
|
);
|
2016-05-03 01:56:13 +00:00
|
|
|
}
|
|
|
|
}
|
2016-06-13 20:41:12 +00:00
|
|
|
|
2018-09-03 17:01:06 +00:00
|
|
|
// Schedule a deferred update to mark local target_page and ?markasread= notifications as read
|
2016-05-03 01:56:13 +00:00
|
|
|
if ( $eventIds ) {
|
2021-05-04 16:06:42 +00:00
|
|
|
DeferredUpdates::addCallableUpdate( static function () use ( $user, $eventIds ) {
|
2016-05-03 01:56:13 +00:00
|
|
|
$notifUser = MWEchoNotifUser::newFromUser( $user );
|
|
|
|
$notifUser->markRead( $eventIds );
|
|
|
|
} );
|
|
|
|
}
|
2014-08-07 00:07:34 +00:00
|
|
|
|
2018-09-03 17:01:06 +00:00
|
|
|
return $subtractions;
|
|
|
|
}
|
|
|
|
|
2021-01-06 17:46:19 +00:00
|
|
|
/**
|
|
|
|
* Determine if a talk page alert should be displayed.
|
|
|
|
* We need to check:
|
|
|
|
* - User actually has new messages
|
|
|
|
* - User is not viewing their user talk page, as user_newtalk will not have been cleared yet.
|
|
|
|
* (bug T107655).
|
|
|
|
*
|
|
|
|
* @param User $user
|
|
|
|
* @param Title $title
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private static function shouldDisplayTalkAlert( $user, $title ) {
|
|
|
|
$userHasNewMessages = MediaWikiServices::getInstance()
|
|
|
|
->getTalkPageNotificationManager()
|
|
|
|
->userHasNewMessages( $user );
|
|
|
|
|
|
|
|
return $userHasNewMessages && !$user->getTalkPage()->equals( $title );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for SkinTemplateNavigation::Universal hook.
|
|
|
|
* Adds "Notifications" items to the notifications content navigation.
|
|
|
|
* SkinTemplate automatically merges these into the personal tools for older skins.
|
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/SkinTemplateNavigation::Universal
|
|
|
|
* @param SkinTemplate $skinTemplate
|
|
|
|
* @param array &$links Array of URLs to append to.
|
|
|
|
*/
|
2022-08-22 22:46:42 +00:00
|
|
|
public function onSkinTemplateNavigation__Universal( $skinTemplate, &$links ): void {
|
2021-01-06 17:46:19 +00:00
|
|
|
$user = $skinTemplate->getUser();
|
|
|
|
if ( !$user->isRegistered() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$title = $skinTemplate->getTitle();
|
|
|
|
$out = $skinTemplate->getOutput();
|
|
|
|
|
|
|
|
$subtractions = self::processMarkAsRead( $user, $out->getRequest(), $title );
|
2018-09-03 17:01:06 +00:00
|
|
|
|
2013-11-13 03:17:04 +00:00
|
|
|
// Add a "My notifications" item to personal URLs
|
2015-07-08 00:10:49 +00:00
|
|
|
$notifUser = MWEchoNotifUser::newFromUser( $user );
|
2018-09-03 17:01:06 +00:00
|
|
|
$msgCount = $notifUser->getMessageCount() - $subtractions[EchoAttributeManager::MESSAGE];
|
|
|
|
$alertCount = $notifUser->getAlertCount() - $subtractions[EchoAttributeManager::ALERT];
|
2016-04-11 08:20:08 +00:00
|
|
|
// But make sure we never show a negative number (T130853)
|
|
|
|
$msgCount = max( 0, $msgCount );
|
|
|
|
$alertCount = max( 0, $alertCount );
|
|
|
|
|
2015-08-13 00:54:16 +00:00
|
|
|
$msgNotificationTimestamp = $notifUser->getLastUnreadMessageTime();
|
|
|
|
$alertNotificationTimestamp = $notifUser->getLastUnreadAlertTime();
|
|
|
|
|
2016-09-15 21:16:06 +00:00
|
|
|
$seenTime = EchoSeenTime::newFromUser( $user );
|
|
|
|
if ( $title->isSpecial( 'Notifications' ) ) {
|
|
|
|
// If this is the Special:Notifications page, seenTime to now
|
|
|
|
$seenTime->setTime( wfTimestamp( TS_MW ), EchoAttributeManager::ALL );
|
|
|
|
}
|
2016-09-23 18:46:44 +00:00
|
|
|
$seenAlertTime = $seenTime->getTime( 'alert', TS_ISO_8601 );
|
|
|
|
$seenMsgTime = $seenTime->getTime( 'message', TS_ISO_8601 );
|
2016-09-13 20:00:35 +00:00
|
|
|
|
2021-01-06 17:46:19 +00:00
|
|
|
$out->addJsConfigVars( 'wgEchoSeenTime', [
|
2015-09-03 01:11:10 +00:00
|
|
|
'alert' => $seenAlertTime,
|
2016-07-22 18:59:10 +00:00
|
|
|
'notice' => $seenMsgTime,
|
2016-12-05 18:51:07 +00:00
|
|
|
] );
|
2015-08-13 00:54:16 +00:00
|
|
|
|
2022-11-02 03:51:15 +00:00
|
|
|
$msgFormattedCount = NotificationController::formatNotificationCount( $msgCount );
|
|
|
|
$alertFormattedCount = NotificationController::formatNotificationCount( $alertCount );
|
2017-03-12 13:04:20 +00:00
|
|
|
|
2015-07-08 00:10:49 +00:00
|
|
|
$url = SpecialPage::getTitleFor( 'Notifications' )->getLocalURL();
|
|
|
|
|
2022-02-08 16:41:48 +00:00
|
|
|
$skinName = strtolower( $skinTemplate->getSkinName() );
|
|
|
|
$isMinervaSkin = $skinName === 'minerva';
|
2015-09-10 17:15:30 +00:00
|
|
|
// HACK: inverted icons only work in the "MediaWiki" OOUI theme
|
|
|
|
// Avoid flashes in skins that don't use it (T111821)
|
2022-02-08 16:41:48 +00:00
|
|
|
$out::setupOOUI( $skinName, $out->getLanguage()->getDir() );
|
|
|
|
$bellIconClass = $isMinervaSkin ? 'oo-ui-icon-bellOutline' : 'oo-ui-icon-bell';
|
2015-09-10 17:15:30 +00:00
|
|
|
|
2019-04-13 18:45:11 +00:00
|
|
|
$msgLinkClasses = [ "mw-echo-notifications-badge", "mw-echo-notification-badge-nojs","oo-ui-icon-tray" ];
|
2022-02-08 16:41:48 +00:00
|
|
|
$alertLinkClasses = [ "mw-echo-notifications-badge", "mw-echo-notification-badge-nojs", $bellIconClass ];
|
2015-08-13 00:54:16 +00:00
|
|
|
|
2015-09-15 22:58:13 +00:00
|
|
|
$hasUnseen = false;
|
2015-07-08 00:10:49 +00:00
|
|
|
if (
|
2015-08-13 00:54:16 +00:00
|
|
|
$msgCount != 0 && // no unread notifications
|
|
|
|
$msgNotificationTimestamp !== false && // should already always be false if count === 0
|
2018-08-25 10:51:14 +00:00
|
|
|
// there are no unseen notifications
|
|
|
|
( $seenMsgTime === null ||
|
|
|
|
$seenMsgTime < $msgNotificationTimestamp->getTimestamp( TS_ISO_8601 ) )
|
2015-07-08 00:10:49 +00:00
|
|
|
) {
|
2015-08-13 00:54:16 +00:00
|
|
|
$msgLinkClasses[] = 'mw-echo-unseen-notifications';
|
2015-09-15 22:58:13 +00:00
|
|
|
$hasUnseen = true;
|
2016-07-20 00:24:17 +00:00
|
|
|
} elseif ( $msgCount === 0 ) {
|
|
|
|
$msgLinkClasses[] = 'mw-echo-notifications-badge-all-read';
|
2013-11-13 03:17:04 +00:00
|
|
|
}
|
2015-08-13 00:54:16 +00:00
|
|
|
|
2016-12-20 18:40:54 +00:00
|
|
|
if ( $msgCount > MWEchoNotifUser::MAX_BADGE_COUNT ) {
|
|
|
|
$msgLinkClasses[] = 'mw-echo-notifications-badge-long-label';
|
|
|
|
}
|
|
|
|
|
2015-08-13 00:54:16 +00:00
|
|
|
if (
|
|
|
|
$alertCount != 0 && // no unread notifications
|
|
|
|
$alertNotificationTimestamp !== false && // should already always be false if count === 0
|
2018-08-25 10:51:14 +00:00
|
|
|
// all notifications have already been seen
|
|
|
|
( $seenAlertTime === null ||
|
|
|
|
$seenAlertTime < $alertNotificationTimestamp->getTimestamp( TS_ISO_8601 ) )
|
2015-08-13 00:54:16 +00:00
|
|
|
) {
|
|
|
|
$alertLinkClasses[] = 'mw-echo-unseen-notifications';
|
2015-09-15 22:58:13 +00:00
|
|
|
$hasUnseen = true;
|
2016-07-20 00:24:17 +00:00
|
|
|
} elseif ( $alertCount === 0 ) {
|
|
|
|
$alertLinkClasses[] = 'mw-echo-notifications-badge-all-read';
|
2015-08-13 00:54:16 +00:00
|
|
|
}
|
|
|
|
|
2016-12-22 21:08:27 +00:00
|
|
|
if ( $alertCount > MWEchoNotifUser::MAX_BADGE_COUNT ) {
|
2016-12-20 18:40:54 +00:00
|
|
|
$alertLinkClasses[] = 'mw-echo-notifications-badge-long-label';
|
|
|
|
}
|
|
|
|
|
2021-06-01 21:14:41 +00:00
|
|
|
$mytalk = $links['user-menu']['mytalk'] ?? false;
|
2021-05-14 20:44:10 +00:00
|
|
|
if (
|
2021-06-01 21:14:41 +00:00
|
|
|
$mytalk &&
|
2021-05-14 20:44:10 +00:00
|
|
|
self::shouldDisplayTalkAlert( $user, $title ) &&
|
|
|
|
MediaWikiServices::getInstance()
|
|
|
|
->getHookContainer()->run( 'BeforeDisplayOrangeAlert', [ $user, $title ] )
|
|
|
|
) {
|
2021-05-27 00:58:05 +00:00
|
|
|
// Create new talk alert inheriting from the talk link data.
|
|
|
|
$links['notifications']['talk-alert'] = array_merge(
|
|
|
|
$links['user-menu']['mytalk'],
|
|
|
|
[
|
2021-07-13 15:00:42 +00:00
|
|
|
// Hardcode id, which is needed to dismiss the talk alert notification
|
|
|
|
'id' => 'pt-talk-alert',
|
2021-07-22 20:19:14 +00:00
|
|
|
// If Vector hook ran anicon will have been copied to the link class.
|
|
|
|
// We must reset it.
|
|
|
|
'link-class' => [],
|
2021-05-27 00:58:05 +00:00
|
|
|
'text' => $skinTemplate->msg( 'echo-new-messages' )->text(),
|
2021-06-08 11:29:59 +00:00
|
|
|
'class' => [ 'mw-echo-alert' ],
|
2021-09-02 22:35:35 +00:00
|
|
|
// unset icon
|
|
|
|
'icon' => '',
|
2021-05-27 00:58:05 +00:00
|
|
|
]
|
|
|
|
);
|
2021-04-30 16:26:51 +00:00
|
|
|
|
|
|
|
// If there's exactly one new user talk message, then link directly to it from the alert.
|
2022-11-02 20:47:04 +00:00
|
|
|
$notificationMapper = new NotificationMapper();
|
2021-04-30 16:26:51 +00:00
|
|
|
$notifications = $notificationMapper->fetchUnreadByUser( $user, 2, null, [ 'edit-user-talk' ] );
|
|
|
|
if ( count( $notifications ) === 1 ) {
|
|
|
|
$presModel = EchoEventPresentationModel::factory(
|
|
|
|
current( $notifications )->getEvent(),
|
|
|
|
$out->getLanguage(),
|
|
|
|
$user
|
|
|
|
);
|
|
|
|
$links['notifications']['talk-alert']['href'] = $presModel->getPrimaryLink()['url'];
|
|
|
|
}
|
2021-05-14 20:44:10 +00:00
|
|
|
}
|
|
|
|
|
2021-01-06 17:46:19 +00:00
|
|
|
$links['notifications']['notifications-alert'] = [
|
2015-07-08 00:10:49 +00:00
|
|
|
'href' => $url,
|
2021-08-19 22:44:31 +00:00
|
|
|
'text' => $skinTemplate->msg( 'echo-notification-alert', $alertCount )->text(),
|
2019-06-11 21:43:05 +00:00
|
|
|
'active' => ( $url == $title->getLocalURL() ),
|
2021-01-06 17:46:19 +00:00
|
|
|
'link-class' => $alertLinkClasses,
|
2022-09-14 22:12:00 +00:00
|
|
|
'icon' => 'bell',
|
2016-12-05 18:51:07 +00:00
|
|
|
'data' => [
|
2022-02-08 16:41:48 +00:00
|
|
|
'event-name' => 'ui.notifications',
|
2016-07-20 00:24:17 +00:00
|
|
|
'counter-num' => $alertCount,
|
2017-03-12 13:04:20 +00:00
|
|
|
'counter-text' => $alertFormattedCount,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
2021-01-06 17:46:19 +00:00
|
|
|
// This item used to be part of personal tools, and much CSS relies on it using this id.
|
|
|
|
'id' => 'pt-notifications-alert',
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2015-08-13 00:54:16 +00:00
|
|
|
|
2021-01-06 17:46:19 +00:00
|
|
|
$links['notifications']['notifications-notice'] = [
|
2016-02-23 09:20:24 +00:00
|
|
|
'href' => $url,
|
2021-08-19 22:44:31 +00:00
|
|
|
'text' => $skinTemplate->msg( 'echo-notification-notice', $msgCount )->text(),
|
2019-06-11 21:43:05 +00:00
|
|
|
'active' => ( $url == $title->getLocalURL() ),
|
2021-01-06 17:46:19 +00:00
|
|
|
'link-class' => $msgLinkClasses,
|
2022-09-14 22:12:00 +00:00
|
|
|
'icon' => 'tray',
|
2016-12-05 18:51:07 +00:00
|
|
|
'data' => [
|
2016-07-20 00:24:17 +00:00
|
|
|
'counter-num' => $msgCount,
|
2017-03-12 13:04:20 +00:00
|
|
|
'counter-text' => $msgFormattedCount,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
2021-01-06 17:46:19 +00:00
|
|
|
// This item used to be part of personal tools, and much CSS relies on it using this id.
|
|
|
|
'id' => 'pt-notifications-notice',
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2015-08-13 00:54:16 +00:00
|
|
|
|
2015-09-15 22:58:13 +00:00
|
|
|
if ( $hasUnseen ) {
|
2019-06-12 22:38:01 +00:00
|
|
|
// Record that the user is going to see an indicator that they have unseen notifications
|
|
|
|
// This is part of tracking how likely users are to click a badge with unseen notifications.
|
|
|
|
// The other part is the 'echo.unseen.click' counter, see ext.echo.init.js.
|
2017-03-17 11:16:28 +00:00
|
|
|
MediaWikiServices::getInstance()->getStatsdDataFactory()->increment( 'echo.unseen' );
|
2015-09-15 22:58:13 +00:00
|
|
|
}
|
2020-08-07 23:41:30 +00:00
|
|
|
}
|
|
|
|
|
2012-05-17 00:29:37 +00:00
|
|
|
/**
|
2013-06-08 01:05:35 +00:00
|
|
|
* Handler for AbortTalkPageEmailNotification hook.
|
2018-10-23 20:35:09 +00:00
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/AbortTalkPageEmailNotification
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param User $targetUser
|
|
|
|
* @param Title $title
|
2013-06-08 01:05:35 +00:00
|
|
|
* @return bool
|
2012-05-17 00:29:37 +00:00
|
|
|
*/
|
2022-08-22 22:46:42 +00:00
|
|
|
public function onAbortTalkPageEmailNotification( $targetUser, $title ) {
|
2013-06-08 01:05:35 +00:00
|
|
|
global $wgEchoNotifications;
|
|
|
|
|
|
|
|
// Send legacy talk page email notification if
|
|
|
|
// 1. echo is disabled for them or
|
|
|
|
// 2. echo talk page notification is disabled
|
2015-06-01 23:22:48 +00:00
|
|
|
if ( !isset( $wgEchoNotifications['edit-user-talk'] ) ) {
|
2013-06-08 01:05:35 +00:00
|
|
|
// Legacy talk page email notification
|
|
|
|
return true;
|
2012-11-16 21:03:57 +00:00
|
|
|
}
|
2013-06-08 01:05:35 +00:00
|
|
|
|
|
|
|
// Echo talk page email notification
|
|
|
|
return false;
|
2012-07-17 22:19:32 +00:00
|
|
|
}
|
2012-07-31 21:18:16 +00:00
|
|
|
|
2014-02-21 01:48:08 +00:00
|
|
|
/**
|
|
|
|
* Handler for AbortWatchlistEmailNotification hook.
|
2018-10-23 20:35:09 +00:00
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/AbortWatchlistEmailNotification
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param User $targetUser
|
|
|
|
* @param Title $title
|
|
|
|
* @param EmailNotification $emailNotification The email notification object that sends non-echo notifications
|
2014-02-21 01:48:08 +00:00
|
|
|
* @return bool
|
|
|
|
*/
|
2022-08-22 22:46:42 +00:00
|
|
|
public function onSendWatchlistEmailNotification( $targetUser, $title, $emailNotification ) {
|
2019-12-09 02:42:55 +00:00
|
|
|
global $wgEchoNotifications, $wgEchoWatchlistNotifications;
|
|
|
|
if ( $wgEchoWatchlistNotifications && isset( $wgEchoNotifications["watchlist-change"] ) ) {
|
|
|
|
// Let echo handle watchlist notifications entirely
|
|
|
|
return false;
|
|
|
|
}
|
2014-02-21 01:48:08 +00:00
|
|
|
// If a user is watching his/her own talk page, do not send talk page watchlist
|
|
|
|
// email notification if the user is receiving Echo talk page notification
|
|
|
|
if ( $title->isTalkPage() && $targetUser->getTalkPage()->equals( $title ) ) {
|
2021-02-25 01:55:40 +00:00
|
|
|
$attributeManager = EchoServices::getInstance()->getAttributeManager();
|
2014-07-22 21:33:22 +00:00
|
|
|
$events = $attributeManager->getUserEnabledEvents( $targetUser, 'email' );
|
2015-06-01 23:22:48 +00:00
|
|
|
if ( in_array( 'edit-user-talk', $events ) ) {
|
2014-02-21 01:48:08 +00:00
|
|
|
// Do not send watchlist email notification, the user will receive an Echo notification
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2015-10-01 13:48:52 +00:00
|
|
|
|
2014-02-21 01:48:08 +00:00
|
|
|
// Proceed to send watchlist email notification
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-22 22:36:48 +00:00
|
|
|
/**
|
|
|
|
* @param array &$modifiedTimes
|
|
|
|
* @param OutputPage $out
|
|
|
|
*/
|
|
|
|
public function onOutputPageCheckLastModified( &$modifiedTimes, $out ) {
|
2021-04-02 22:39:15 +00:00
|
|
|
$req = $out->getRequest();
|
|
|
|
if ( $req->getRawVal( 'action' ) === 'raw' || $req->getRawVal( 'action' ) === 'render' ) {
|
|
|
|
// Optimisation: Avoid expensive EchoSeenTime compute on non-skin responses (T279213)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-11 00:16:14 +00:00
|
|
|
$user = $out->getUser();
|
2020-12-18 02:32:42 +00:00
|
|
|
if ( $user->isRegistered() ) {
|
2016-05-10 23:56:07 +00:00
|
|
|
$notifUser = MWEchoNotifUser::newFromUser( $user );
|
|
|
|
$lastUpdate = $notifUser->getGlobalUpdateTime();
|
|
|
|
if ( $lastUpdate !== false ) {
|
2016-08-15 23:41:44 +00:00
|
|
|
$modifiedTimes['notifications-global'] = $lastUpdate;
|
2016-05-10 23:56:07 +00:00
|
|
|
}
|
2016-09-06 17:38:01 +00:00
|
|
|
|
2016-09-13 20:00:35 +00:00
|
|
|
$modifiedTimes['notifications-seen-alert'] = EchoSeenTime::newFromUser( $user )->getTime( 'alert' );
|
|
|
|
$modifiedTimes['notifications-seen-message'] = EchoSeenTime::newFromUser( $user )->getTime( 'message' );
|
2016-05-10 23:56:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-01 21:59:53 +00:00
|
|
|
/**
|
2013-05-05 00:49:08 +00:00
|
|
|
* Handler for GetNewMessagesAlert hook.
|
|
|
|
* We're using the GetNewMessagesAlert hook instead of the
|
|
|
|
* ArticleEditUpdateNewTalk hook since we still want the user_newtalk data
|
2018-11-16 00:23:25 +00:00
|
|
|
* to be updated and available to client-side tools and the API.
|
2018-10-30 16:52:08 +00:00
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/GetNewMessagesAlert
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param string &$newMessagesAlert An alert that the user has new messages
|
2013-05-05 00:49:08 +00:00
|
|
|
* or an empty string if the user does not (empty by default)
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param array $newtalks This will be empty if the user has no new messages
|
2013-05-05 00:49:08 +00:00
|
|
|
* or an Array containing links and revisions if there are new messages
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param User $user The user who is loading the page
|
2018-06-17 16:56:02 +00:00
|
|
|
* @param OutputPage $out
|
2013-05-05 00:49:08 +00:00
|
|
|
* @return bool Should return false to prevent the new messages alert (OBOD)
|
|
|
|
* or true to allow the new messages alert
|
2012-11-01 21:59:53 +00:00
|
|
|
*/
|
2022-08-22 22:46:42 +00:00
|
|
|
public function onGetNewMessagesAlert( &$newMessagesAlert, $newtalks, $user, $out ) {
|
2013-03-20 01:10:23 +00:00
|
|
|
global $wgEchoNotifications;
|
2013-06-08 01:05:35 +00:00
|
|
|
|
2012-11-01 21:59:53 +00:00
|
|
|
// If the user has the notifications flyout turned on and is receiving
|
2013-05-05 00:49:08 +00:00
|
|
|
// notifications for talk page messages, disable the new messages alert.
|
2020-12-18 02:32:42 +00:00
|
|
|
if ( $user->isRegistered()
|
2013-03-20 01:10:23 +00:00
|
|
|
&& isset( $wgEchoNotifications['edit-user-talk'] )
|
2022-04-08 00:28:15 +00:00
|
|
|
&& MWHooks::run( 'EchoCanAbortNewMessagesAlert' )
|
2013-03-20 01:10:23 +00:00
|
|
|
) {
|
2013-05-05 00:49:08 +00:00
|
|
|
// hide new messages alert
|
2012-11-01 21:59:53 +00:00
|
|
|
return false;
|
|
|
|
} else {
|
2013-05-05 00:49:08 +00:00
|
|
|
// show new messages alert
|
2012-11-01 21:59:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
2012-07-31 20:44:43 +00:00
|
|
|
}
|
2012-07-18 19:39:33 +00:00
|
|
|
|
2018-03-26 18:54:03 +00:00
|
|
|
/**
|
2020-04-20 23:03:54 +00:00
|
|
|
* Handler for RollbackComplete hook.
|
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/RollbackComplete
|
2018-03-26 18:54:03 +00:00
|
|
|
*
|
|
|
|
* @param WikiPage $wikiPage The article that was edited
|
2020-04-20 23:03:54 +00:00
|
|
|
* @param UserIdentity $agent The user who did the rollback
|
|
|
|
* @param RevisionRecord $newRevision The revision the page was reverted back to
|
|
|
|
* @param RevisionRecord $oldRevision The revision of the top edit that was reverted
|
2018-03-26 18:54:03 +00:00
|
|
|
*/
|
2022-08-22 22:46:42 +00:00
|
|
|
public function onRollbackComplete(
|
|
|
|
$wikiPage,
|
|
|
|
$agent,
|
|
|
|
$newRevision,
|
|
|
|
$oldRevision
|
2020-04-20 23:03:54 +00:00
|
|
|
) {
|
2020-07-21 11:15:09 +00:00
|
|
|
$revertedUser = $oldRevision->getUser();
|
2019-04-17 15:46:06 +00:00
|
|
|
$latestRevision = $wikiPage->getRevisionRecord();
|
2018-03-26 18:54:03 +00:00
|
|
|
|
|
|
|
if (
|
2020-07-21 11:18:29 +00:00
|
|
|
$revertedUser &&
|
|
|
|
$revertedUser->getId() && // No notifications for anonymous users
|
2020-04-20 23:03:54 +00:00
|
|
|
!$oldRevision->hasSameContent( $newRevision ) // No notifications for null rollbacks
|
2018-03-26 18:54:03 +00:00
|
|
|
) {
|
|
|
|
EchoEvent::create( [
|
|
|
|
'type' => 'reverted',
|
|
|
|
'title' => $wikiPage->getTitle(),
|
|
|
|
'extra' => [
|
2018-03-26 22:32:30 +00:00
|
|
|
'revid' => $latestRevision->getId(),
|
2020-07-21 11:18:29 +00:00
|
|
|
'reverted-user-id' => $revertedUser->getId(),
|
2018-03-26 18:54:03 +00:00
|
|
|
'reverted-revision-id' => $oldRevision->getId(),
|
|
|
|
'method' => 'rollback',
|
|
|
|
],
|
2021-12-19 01:16:52 +00:00
|
|
|
'agent' => $agent,
|
2018-03-26 18:54:03 +00:00
|
|
|
] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-12 22:12:22 +00:00
|
|
|
/**
|
|
|
|
* Handler for UserSaveSettings hook.
|
2018-10-30 16:52:08 +00:00
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/UserSaveSettings
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param User $user whose settings were saved
|
2013-04-12 22:12:22 +00:00
|
|
|
*/
|
2022-08-22 22:46:42 +00:00
|
|
|
public function onUserSaveSettings( $user ) {
|
2013-11-21 00:31:01 +00:00
|
|
|
// Extensions like AbuseFilter might create an account, but
|
|
|
|
// the tables we need might not exist. Bug 57335
|
|
|
|
if ( !defined( 'MW_UPDATER' ) ) {
|
|
|
|
// Reset the notification count since it may have changed due to user
|
|
|
|
// option changes. This covers both explicit changes in the preferences
|
|
|
|
// and changes made through the options API (since both call this hook).
|
2021-05-04 16:06:42 +00:00
|
|
|
DeferredUpdates::addCallableUpdate( static function () use ( $user ) {
|
2016-12-08 20:50:03 +00:00
|
|
|
MWEchoNotifUser::newFromUser( $user )->resetNotificationCount();
|
|
|
|
} );
|
2013-11-21 00:31:01 +00:00
|
|
|
}
|
2013-04-12 22:12:22 +00:00
|
|
|
}
|
2013-04-26 16:08:40 +00:00
|
|
|
|
2021-07-27 17:53:58 +00:00
|
|
|
/**
|
|
|
|
* Some of Echo's subscription user preferences are mapped to existing user preferences defined in
|
|
|
|
* core MediaWiki. This returns the map of Echo preference names to core preference names.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function getVirtualUserOptions() {
|
|
|
|
global $wgEchoWatchlistNotifications;
|
|
|
|
$options = [];
|
|
|
|
$options['echo-subscriptions-email-edit-user-talk'] = 'enotifusertalkpages';
|
|
|
|
if ( $wgEchoWatchlistNotifications ) {
|
|
|
|
$options['echo-subscriptions-email-watchlist'] = 'enotifwatchlistpages';
|
|
|
|
$options['echo-subscriptions-email-minor-watchlist'] = 'enotifminoredits';
|
|
|
|
}
|
|
|
|
return $options;
|
|
|
|
}
|
|
|
|
|
2013-04-26 16:08:40 +00:00
|
|
|
/**
|
2022-01-09 19:16:10 +00:00
|
|
|
* Handler for LoadUserOptions hook.
|
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/LoadUserOptions
|
2021-07-26 16:28:06 +00:00
|
|
|
* @param UserIdentity $user User whose options were loaded
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param array &$options Options can be modified
|
2013-04-26 16:08:40 +00:00
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public function onLoadUserOptions( UserIdentity $user, &$options ): void {
|
2021-07-27 17:53:58 +00:00
|
|
|
foreach ( self::getVirtualUserOptions() as $echoPref => $mwPref ) {
|
|
|
|
// Use the existing core option's value for the Echo option
|
|
|
|
if ( isset( $options[ $mwPref ] ) ) {
|
|
|
|
$options[ $echoPref ] = $options[ $mwPref ];
|
2019-12-09 02:42:55 +00:00
|
|
|
}
|
|
|
|
}
|
2013-04-26 16:08:40 +00:00
|
|
|
}
|
2013-04-30 02:53:33 +00:00
|
|
|
|
|
|
|
/**
|
2021-07-27 13:57:21 +00:00
|
|
|
* Handler for SaveUserOptions hook.
|
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/SaveUserOptions
|
|
|
|
* @param UserIdentity $user User whose options are being saved
|
|
|
|
* @param array &$modifiedOptions Options can be modified
|
2022-08-22 22:31:10 +00:00
|
|
|
* @param array $originalOptions
|
2013-04-30 02:53:33 +00:00
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public function onSaveUserOptions( UserIdentity $user, array &$modifiedOptions, array $originalOptions ) {
|
2021-07-27 17:53:58 +00:00
|
|
|
foreach ( self::getVirtualUserOptions() as $echoPref => $mwPref ) {
|
|
|
|
// Save virtual option values in corresponding real option values
|
2021-07-27 13:57:21 +00:00
|
|
|
if ( isset( $modifiedOptions[ $echoPref ] ) ) {
|
|
|
|
$modifiedOptions[ $mwPref ] = $modifiedOptions[ $echoPref ];
|
|
|
|
unset( $modifiedOptions[ $echoPref ] );
|
2019-12-09 02:42:55 +00:00
|
|
|
}
|
|
|
|
}
|
2013-04-30 02:53:33 +00:00
|
|
|
}
|
2013-05-24 22:51:47 +00:00
|
|
|
|
2017-11-13 16:36:11 +00:00
|
|
|
/**
|
|
|
|
* Convert all values in an array to integers and filter out zeroes.
|
|
|
|
*
|
|
|
|
* @param array $numbers
|
|
|
|
*
|
|
|
|
* @return int[]
|
|
|
|
*/
|
|
|
|
protected static function mapToInt( array $numbers ) {
|
|
|
|
$data = [];
|
|
|
|
|
|
|
|
foreach ( $numbers as $value ) {
|
|
|
|
$int = intval( $value );
|
|
|
|
if ( $int === 0 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$data[] = $int;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2013-05-24 22:51:47 +00:00
|
|
|
/**
|
|
|
|
* Handler for UserClearNewTalkNotification hook.
|
2018-10-30 16:52:08 +00:00
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/UserClearNewTalkNotification
|
2020-05-23 05:06:12 +00:00
|
|
|
* @param UserIdentity $user User whose talk page notification should be marked as read
|
2022-08-22 22:31:10 +00:00
|
|
|
* @param int $oldid
|
2013-05-24 22:51:47 +00:00
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public function onUserClearNewTalkNotification( $user, $oldid ) {
|
2020-05-23 05:06:12 +00:00
|
|
|
if ( $user->isRegistered() ) {
|
2021-12-19 01:16:52 +00:00
|
|
|
DeferredUpdates::addCallableUpdate( static function () use ( $user ) {
|
|
|
|
MWEchoNotifUser::newFromUser( $user )->clearUserTalkNotifications();
|
2015-08-26 23:31:58 +00:00
|
|
|
} );
|
2013-05-24 22:51:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-19 20:49:34 +00:00
|
|
|
/**
|
|
|
|
* Handler for EmailUserComplete hook.
|
|
|
|
* @see https://www.mediawiki.org/wiki/Manual:Hooks/EmailUserComplete
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param MailAddress $address Adress of receiving user
|
|
|
|
* @param MailAddress $from Adress of sending user
|
|
|
|
* @param string $subject Subject of the mail
|
|
|
|
* @param string $text Text of the mail
|
2015-09-19 20:49:34 +00:00
|
|
|
*/
|
2022-08-22 22:31:10 +00:00
|
|
|
public function onEmailUserComplete( $address, $from, $subject, $text ) {
|
2015-09-19 20:49:34 +00:00
|
|
|
if ( $from->name === $address->name ) {
|
|
|
|
// nothing to notify
|
2019-12-27 11:11:11 +00:00
|
|
|
return;
|
2015-09-19 20:49:34 +00:00
|
|
|
}
|
|
|
|
$userTo = User::newFromName( $address->name );
|
|
|
|
$userFrom = User::newFromName( $from->name );
|
|
|
|
|
2016-02-10 22:09:41 +00:00
|
|
|
$autoSubject = wfMessage( 'defemailsubject', $from->name )->inContentLanguage()->text();
|
|
|
|
if ( $subject === $autoSubject ) {
|
2018-08-25 10:51:14 +00:00
|
|
|
$autoFooter = "\n\n-- \n" . wfMessage( 'emailuserfooter', $from->name, $address->name )
|
|
|
|
->inContentLanguage()->text();
|
2016-02-23 21:26:55 +00:00
|
|
|
$textWithoutFooter = preg_replace( '/' . preg_quote( $autoFooter, '/' ) . '$/', '', $text );
|
2018-08-13 21:29:50 +00:00
|
|
|
$preview = MediaWikiServices::getInstance()->getContentLanguage()
|
|
|
|
->truncateForVisual( $textWithoutFooter, 125 );
|
2016-02-10 22:09:41 +00:00
|
|
|
} else {
|
|
|
|
$preview = $subject;
|
|
|
|
}
|
|
|
|
|
2016-12-05 18:51:07 +00:00
|
|
|
EchoEvent::create( [
|
2015-09-19 20:49:34 +00:00
|
|
|
'type' => 'emailuser',
|
2016-12-05 18:51:07 +00:00
|
|
|
'extra' => [
|
2015-09-19 20:49:34 +00:00
|
|
|
'to-user-id' => $userTo->getId(),
|
2016-02-10 22:09:41 +00:00
|
|
|
'preview' => $preview,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
2015-09-19 20:49:34 +00:00
|
|
|
'agent' => $userFrom,
|
2016-12-05 18:51:07 +00:00
|
|
|
] );
|
2015-09-19 20:49:34 +00:00
|
|
|
}
|
|
|
|
|
2014-08-26 03:09:46 +00:00
|
|
|
/**
|
|
|
|
* For integration with the UserMerge extension.
|
|
|
|
*
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param array &$updateFields
|
2014-08-26 03:09:46 +00:00
|
|
|
*/
|
|
|
|
public static function onUserMergeAccountFields( &$updateFields ) {
|
|
|
|
// array( tableName, idField, textField )
|
2021-05-03 07:28:02 +00:00
|
|
|
$dbw = MWEchoDbFactory::newFromDefault()->getEchoDb( DB_PRIMARY );
|
2016-12-05 18:51:07 +00:00
|
|
|
$updateFields[] = [ 'echo_event', 'event_agent_id', 'db' => $dbw ];
|
|
|
|
$updateFields[] = [ 'echo_notification', 'notification_user', 'db' => $dbw, 'options' => [ 'IGNORE' ] ];
|
|
|
|
$updateFields[] = [ 'echo_email_batch', 'eeb_user_id', 'db' => $dbw, 'options' => [ 'IGNORE' ] ];
|
2014-08-26 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function onMergeAccountFromTo( User &$oldUser, User &$newUser ) {
|
2018-09-18 12:56:59 +00:00
|
|
|
$method = __METHOD__;
|
2021-05-04 16:06:42 +00:00
|
|
|
DeferredUpdates::addCallableUpdate( static function () use ( $oldUser, $newUser, $method ) {
|
2020-12-22 15:36:58 +00:00
|
|
|
if ( $newUser->isRegistered() ) {
|
2018-09-18 12:56:59 +00:00
|
|
|
// Select notifications that are now sent to the same user
|
2021-05-03 07:28:02 +00:00
|
|
|
$dbw = MWEchoDbFactory::newFromDefault()->getEchoDb( DB_PRIMARY );
|
2021-02-25 01:55:40 +00:00
|
|
|
$attributeManager = EchoServices::getInstance()->getAttributeManager();
|
2018-09-18 12:56:59 +00:00
|
|
|
$selfIds = $dbw->selectFieldValues(
|
|
|
|
[ 'echo_notification', 'echo_event' ],
|
|
|
|
'event_id',
|
|
|
|
[
|
|
|
|
'notification_user' => $newUser->getId(),
|
|
|
|
'notification_event = event_id',
|
|
|
|
'notification_user = event_agent_id',
|
2019-02-15 20:25:27 +00:00
|
|
|
'event_type NOT IN (' . $dbw->makeList( $attributeManager->getNotifyAgentEvents() ) . ')'
|
2018-09-18 12:56:59 +00:00
|
|
|
],
|
|
|
|
$method
|
|
|
|
) ?: [];
|
|
|
|
|
|
|
|
// Select newer welcome notification(s)
|
|
|
|
$welcomeIds = $dbw->selectFieldValues(
|
|
|
|
[ 'echo_notification', 'echo_event' ],
|
|
|
|
'event_id',
|
|
|
|
[
|
|
|
|
'notification_user' => $newUser->getId(),
|
|
|
|
'notification_event = event_id',
|
|
|
|
'event_type' => 'welcome',
|
|
|
|
],
|
|
|
|
$method,
|
|
|
|
[
|
|
|
|
'ORDER BY' => 'notification_timestamp ASC',
|
|
|
|
'OFFSET' => 1,
|
|
|
|
]
|
|
|
|
) ?: [];
|
|
|
|
|
|
|
|
// Select newer milestone notifications (per milestone level)
|
|
|
|
$counts = [];
|
|
|
|
$thankYouIds = [];
|
|
|
|
$thankYouRows = $dbw->select(
|
|
|
|
[ 'echo_notification', 'echo_event' ],
|
2019-03-02 20:25:33 +00:00
|
|
|
EchoEvent::selectFields(),
|
2018-09-18 12:56:59 +00:00
|
|
|
[
|
|
|
|
'notification_user' => $newUser->getId(),
|
|
|
|
'notification_event = event_id',
|
|
|
|
'event_type' => 'thank-you-edit',
|
|
|
|
],
|
|
|
|
$method,
|
|
|
|
[ 'ORDER BY' => 'notification_timestamp ASC' ]
|
|
|
|
) ?: [];
|
|
|
|
foreach ( $thankYouRows as $row ) {
|
|
|
|
$event = EchoEvent::newFromRow( $row );
|
|
|
|
$editCount = $event ? $event->getExtraParam( 'editCount' ) : null;
|
|
|
|
if ( $editCount ) {
|
|
|
|
if ( isset( $counts[$editCount] ) ) {
|
|
|
|
$thankYouIds[] = $row->event_id;
|
|
|
|
} else {
|
|
|
|
$counts[$editCount] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete notifications
|
|
|
|
$ids = array_merge( $selfIds, $welcomeIds, $thankYouIds );
|
2020-01-29 04:01:51 +00:00
|
|
|
if ( $ids !== [] ) {
|
2018-09-18 12:56:59 +00:00
|
|
|
$dbw->delete(
|
|
|
|
'echo_notification',
|
|
|
|
[
|
|
|
|
'notification_user' => $newUser->getId(),
|
|
|
|
'notification_event' => $ids
|
|
|
|
],
|
|
|
|
$method
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-25 17:45:49 +00:00
|
|
|
MWEchoNotifUser::newFromUser( $oldUser )->resetNotificationCount();
|
2020-12-22 15:36:58 +00:00
|
|
|
if ( $newUser->isRegistered() ) {
|
2018-05-25 17:45:49 +00:00
|
|
|
MWEchoNotifUser::newFromUser( $newUser )->resetNotificationCount();
|
2016-12-08 20:50:03 +00:00
|
|
|
}
|
|
|
|
} );
|
2014-08-26 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function onUserMergeAccountDeleteTables( &$tables ) {
|
2021-05-03 07:28:02 +00:00
|
|
|
$dbw = MWEchoDbFactory::newFromDefault()->getEchoDb( DB_PRIMARY );
|
2016-12-05 18:51:07 +00:00
|
|
|
$tables['echo_notification'] = [ 'notification_user', 'db' => $dbw ];
|
|
|
|
$tables['echo_email_batch'] = [ 'eeb_user_id', 'db' => $dbw ];
|
2014-08-26 03:09:46 +00:00
|
|
|
}
|
2015-12-08 18:54:44 +00:00
|
|
|
|
2017-06-22 08:03:10 +00:00
|
|
|
/**
|
|
|
|
* Sets custom login message for redirect from notification page
|
|
|
|
*
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param array &$messages
|
2017-06-22 08:03:10 +00:00
|
|
|
*/
|
2022-08-22 22:36:48 +00:00
|
|
|
public function onLoginFormValidErrorMessages( array &$messages ) {
|
2015-12-08 18:54:44 +00:00
|
|
|
$messages[] = 'echo-notification-loginrequired';
|
|
|
|
}
|
|
|
|
|
2022-05-20 02:11:31 +00:00
|
|
|
public static function getConfigVars( RL\Context $context, Config $config ) {
|
2019-07-03 23:04:14 +00:00
|
|
|
return [
|
|
|
|
'EchoMaxNotificationCount' => MWEchoNotifUser::MAX_BADGE_COUNT,
|
2019-07-18 21:51:19 +00:00
|
|
|
'EchoPollForUpdates' => $config->get( 'EchoPollForUpdates' )
|
2019-07-03 23:04:14 +00:00
|
|
|
];
|
2016-02-18 22:36:58 +00:00
|
|
|
}
|
|
|
|
|
2022-05-20 02:11:31 +00:00
|
|
|
public static function getLoggerConfigVars( RL\Context $context, Config $config ) {
|
2019-07-20 07:07:05 +00:00
|
|
|
$schemas = $config->get( 'EchoEventLoggingSchemas' );
|
|
|
|
return [
|
|
|
|
'EchoInteractionLogging' => $schemas['EchoInteraction']['enabled'] &&
|
|
|
|
ExtensionRegistry::getInstance()->isLoaded( 'EventLogging' ),
|
|
|
|
'EchoEventLoggingVersion' => $config->get( 'EchoEventLoggingVersion' )
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2019-03-11 13:25:18 +00:00
|
|
|
/**
|
2022-08-22 22:36:48 +00:00
|
|
|
* @param WikiPage $article
|
|
|
|
* @param User $user
|
2019-03-11 13:25:18 +00:00
|
|
|
* @param string $reason
|
|
|
|
* @param int $articleId
|
|
|
|
* @param Content|null $content
|
|
|
|
* @param LogEntry $logEntry
|
2022-08-22 22:36:48 +00:00
|
|
|
* @param int $archivedRevisionCount
|
2019-03-11 13:25:18 +00:00
|
|
|
*/
|
2022-08-22 22:36:48 +00:00
|
|
|
public function onArticleDeleteComplete(
|
|
|
|
$article,
|
|
|
|
$user,
|
2018-08-25 10:51:14 +00:00
|
|
|
$reason,
|
|
|
|
$articleId,
|
2022-08-22 22:36:48 +00:00
|
|
|
$content,
|
|
|
|
$logEntry,
|
|
|
|
$archivedRevisionCount
|
2018-08-25 10:51:14 +00:00
|
|
|
) {
|
2022-04-08 00:28:15 +00:00
|
|
|
DeferredUpdates::addCallableUpdate( static function () use ( $articleId ) {
|
2022-11-02 20:47:04 +00:00
|
|
|
$eventMapper = new EventMapper();
|
2016-07-27 14:52:18 +00:00
|
|
|
$eventIds = $eventMapper->fetchIdsByPage( $articleId );
|
2022-11-02 03:51:15 +00:00
|
|
|
ModerationController::moderate( $eventIds, true );
|
2016-07-27 14:52:18 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2022-08-22 22:36:48 +00:00
|
|
|
/**
|
|
|
|
* @param Title $title
|
|
|
|
* @param bool $create
|
|
|
|
* @param string $comment
|
|
|
|
* @param int $oldPageId
|
|
|
|
* @param array $restoredPages
|
|
|
|
*/
|
|
|
|
public function onArticleUndelete( $title, $create, $comment, $oldPageId, $restoredPages ) {
|
2016-07-27 14:52:18 +00:00
|
|
|
if ( $create ) {
|
2022-04-08 00:28:15 +00:00
|
|
|
DeferredUpdates::addCallableUpdate( static function () use ( $oldPageId ) {
|
2022-11-02 20:47:04 +00:00
|
|
|
$eventMapper = new EventMapper();
|
2016-07-27 14:52:18 +00:00
|
|
|
$eventIds = $eventMapper->fetchIdsByPage( $oldPageId );
|
2022-11-02 03:51:15 +00:00
|
|
|
ModerationController::moderate( $eventIds, false );
|
2016-07-27 14:52:18 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-01 17:49:31 +00:00
|
|
|
/**
|
|
|
|
* Handler for SpecialMuteModifyFormFields hook
|
|
|
|
*
|
2021-06-24 19:21:49 +00:00
|
|
|
* @param UserIdentity|null $target
|
2020-10-30 14:26:36 +00:00
|
|
|
* @param User $user
|
2019-07-01 17:49:31 +00:00
|
|
|
* @param array &$fields
|
|
|
|
*/
|
2020-10-30 14:26:36 +00:00
|
|
|
public static function onSpecialMuteModifyFormFields( $target, $user, &$fields ) {
|
2021-12-01 18:29:00 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
$echoPerUserBlacklist = $services->getMainConfig()->get( 'EchoPerUserBlacklist' );
|
2019-07-01 17:49:31 +00:00
|
|
|
if ( $echoPerUserBlacklist ) {
|
2021-12-01 18:29:00 +00:00
|
|
|
$id = $target ? $services->getCentralIdLookup()->centralIdFromLocalUser( $target ) : 0;
|
|
|
|
$list = MultiUsernameFilter::splitIds(
|
|
|
|
$services->getUserOptionsLookup()->getOption( $user, 'echo-notifications-blacklist' )
|
|
|
|
);
|
2019-07-01 17:49:31 +00:00
|
|
|
$fields[ 'echo-notifications-blacklist'] = [
|
|
|
|
'type' => 'check',
|
2020-05-03 07:27:58 +00:00
|
|
|
'label-message' => [
|
|
|
|
'echo-specialmute-label-mute-notifications',
|
2020-06-25 10:53:12 +00:00
|
|
|
$target ? $target->getName() : ''
|
2020-05-03 07:27:58 +00:00
|
|
|
],
|
2020-10-30 14:26:36 +00:00
|
|
|
'default' => in_array( $id, $list, true ),
|
2019-07-01 17:49:31 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
2019-12-09 02:42:55 +00:00
|
|
|
|
|
|
|
/**
|
2020-06-03 09:20:46 +00:00
|
|
|
* @param RecentChange $change
|
|
|
|
* @return bool|void
|
|
|
|
* @throws MWException
|
2019-12-09 02:42:55 +00:00
|
|
|
*/
|
2020-06-03 09:20:46 +00:00
|
|
|
public function onRecentChange_save( $change ) {
|
|
|
|
if ( !$this->config->get( 'EchoWatchlistNotifications' ) ) {
|
2019-12-09 02:42:55 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( $change->getAttribute( 'rc_minor' ) ) {
|
|
|
|
$type = 'minor-watchlist-change';
|
|
|
|
} else {
|
|
|
|
$type = 'watchlist-change';
|
|
|
|
}
|
|
|
|
EchoEvent::create( [
|
|
|
|
'type' => $type,
|
|
|
|
'title' => $change->getTitle(),
|
|
|
|
'extra' => [
|
2021-11-03 14:47:50 +00:00
|
|
|
'page_title' => $change->getPage()->getDBkey(),
|
|
|
|
'page_namespace' => $change->getPage()->getNamespace(),
|
2019-12-09 02:42:55 +00:00
|
|
|
'revid' => $change->getAttribute( "rc_this_oldid" ),
|
|
|
|
'logid' => $change->getAttribute( "rc_logid" ),
|
|
|
|
'status' => $change->mExtra["pageStatus"],
|
2020-06-01 02:17:13 +00:00
|
|
|
'timestamp' => $change->getAttribute( "rc_timestamp" ),
|
|
|
|
'emailonce' => $this->config->get( 'EchoWatchlistEmailOncePerPage' )
|
2019-12-09 02:42:55 +00:00
|
|
|
],
|
2021-12-19 01:16:52 +00:00
|
|
|
'agent' => $change->getPerformerIdentity(),
|
2019-12-09 02:42:55 +00:00
|
|
|
] );
|
|
|
|
}
|
2020-05-15 17:19:03 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook handler for ApiMain::moduleManager.
|
|
|
|
* Used here to put the echopushsubscriptions API module behind our push feature flag.
|
|
|
|
* TODO: Register this the usual way in extension.json when we don't need the feature flag
|
|
|
|
* anymore.
|
|
|
|
* @param ApiModuleManager $moduleManager
|
|
|
|
*/
|
2022-08-22 22:46:42 +00:00
|
|
|
public function onApiMain__ModuleManager( $moduleManager ) {
|
2020-05-15 17:19:03 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
$echoConfig = $services->getConfigFactory()->makeConfig( 'Echo' );
|
|
|
|
$pushEnabled = $echoConfig->get( 'EchoEnablePush' );
|
|
|
|
if ( $pushEnabled ) {
|
|
|
|
$moduleManager->addModule(
|
|
|
|
'echopushsubscriptions',
|
|
|
|
'action',
|
2022-04-08 00:28:15 +00:00
|
|
|
ApiEchoPushSubscriptions::class
|
2020-05-15 17:19:03 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-06-03 09:20:46 +00:00
|
|
|
|
2012-07-18 16:28:41 +00:00
|
|
|
}
|