Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 98 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
Notifier | |
0.00% |
0 / 97 |
|
0.00% |
0 / 4 |
1640 | |
0.00% |
0 / 1 |
notifyWithNotification | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
notifyWithEmail | |
0.00% |
0 / 52 |
|
0.00% |
0 / 1 |
600 | |||
getBundleRules | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
132 | |||
generateEmail | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Notifications; |
4 | |
5 | use MailAddress; |
6 | use MediaWiki\Extension\Notifications\Formatters\EchoHtmlEmailFormatter; |
7 | use MediaWiki\Extension\Notifications\Formatters\EchoPlainTextEmailFormatter; |
8 | use MediaWiki\Extension\Notifications\Hooks\HookRunner; |
9 | use MediaWiki\Extension\Notifications\Model\Event; |
10 | use MediaWiki\Extension\Notifications\Model\Notification; |
11 | use MediaWiki\MediaWikiServices; |
12 | use MediaWiki\User\User; |
13 | use UserMailer; |
14 | |
15 | class Notifier { |
16 | /** |
17 | * Record a Notification for an Event |
18 | * Currently used for web-based notifications. |
19 | * |
20 | * @param User $user User to notify. |
21 | * @param Event $event Event to notify about. |
22 | */ |
23 | public static function notifyWithNotification( $user, $event ) { |
24 | // Only create the notification if the user wants to receive that type |
25 | // of notification, and they are eligible to receive it. See bug 47664. |
26 | $attributeManager = Services::getInstance()->getAttributeManager(); |
27 | $userWebNotifications = $attributeManager->getUserEnabledEvents( $user, 'web' ); |
28 | if ( !in_array( $event->getType(), $userWebNotifications ) ) { |
29 | return; |
30 | } |
31 | |
32 | Notification::create( [ 'user' => $user, 'event' => $event ] ); |
33 | } |
34 | |
35 | /** |
36 | * Send a Notification to a user by email |
37 | * |
38 | * @param User $user User to notify. |
39 | * @param Event $event Event to notify about. |
40 | * @return bool |
41 | */ |
42 | public static function notifyWithEmail( $user, $event ) { |
43 | global $wgEnableEmail, $wgBlockDisablesLogin, $wgEchoWatchlistEmailOncePerPage, $wgEnotifMinorEdits; |
44 | $services = MediaWikiServices::getInstance(); |
45 | $userOptionsLookup = $services->getUserOptionsLookup(); |
46 | |
47 | if ( |
48 | // Email is globally disabled |
49 | !$wgEnableEmail || |
50 | // User does not have a valid and confirmed email address |
51 | !$user->isEmailConfirmed() || |
52 | // User has disabled Echo emails |
53 | $userOptionsLookup->getOption( $user, 'echo-email-frequency' ) < 0 || |
54 | // User is blocked and cannot log in (T199993) |
55 | ( $wgBlockDisablesLogin && $user->getBlock() ) |
56 | ) { |
57 | return false; |
58 | } |
59 | |
60 | $type = $event->getType(); |
61 | if ( $type === 'edit-user-talk' ) { |
62 | $extra = $event->getExtra(); |
63 | if ( !empty( $extra['minoredit'] ) ) { |
64 | if ( !$wgEnotifMinorEdits || !$userOptionsLookup->getOption( $user, 'enotifminoredits' ) ) { |
65 | // Do not send talk page notification email |
66 | return false; |
67 | } |
68 | } |
69 | // Mimic core code of only sending watchlist notification emails once per page |
70 | } elseif ( $type === "watchlist-change" || $type === "minor-watchlist-change" ) { |
71 | // Don't care about rate limiting |
72 | if ( $wgEchoWatchlistEmailOncePerPage ) { |
73 | $store = $services->getWatchedItemStore(); |
74 | $ts = $store->getWatchedItem( $user, $event->getTitle() )->getNotificationTimestamp(); |
75 | // if (ts != null) is not sufficient because, if $wgEchoUseJobQueue is set, |
76 | // wl_notificationtimestamp will have already been set for the new edit |
77 | // by the time this code runs. |
78 | if ( $ts !== null && $ts !== $event->getExtraParam( "timestamp" ) ) { |
79 | // User has already seen an email for this page before |
80 | return false; |
81 | } |
82 | } |
83 | } |
84 | |
85 | $hookRunner = new HookRunner( $services->getHookContainer() ); |
86 | // Final check on whether to send email for this user & event |
87 | if ( !$hookRunner->onEchoAbortEmailNotification( $user, $event ) ) { |
88 | return false; |
89 | } |
90 | |
91 | $attributeManager = Services::getInstance()->getAttributeManager(); |
92 | $userEmailNotifications = $attributeManager->getUserEnabledEvents( $user, 'email' ); |
93 | // See if the user wants to receive emails for this category or the user is eligible to receive this email |
94 | if ( in_array( $event->getType(), $userEmailNotifications ) ) { |
95 | global $wgEchoEnableEmailBatch, $wgEchoNotifications, $wgPasswordSender, $wgNoReplyAddress; |
96 | |
97 | $priority = $attributeManager->getNotificationPriority( $event->getType() ); |
98 | |
99 | $bundleString = $bundleHash = ''; |
100 | |
101 | // We should have bundling for email digest as long as either web or email bundling is on, |
102 | // for example, talk page email bundling is off, but if a user decides to receive email |
103 | // digest, we should bundle those messages |
104 | if ( !empty( $wgEchoNotifications[$event->getType()]['bundle']['web'] ) || |
105 | !empty( $wgEchoNotifications[$event->getType()]['bundle']['email'] ) |
106 | ) { |
107 | self::getBundleRules( $event, $bundleString ); |
108 | } |
109 | if ( $bundleString ) { |
110 | $bundleHash = md5( $bundleString ); |
111 | } |
112 | |
113 | // email digest notification ( weekly or daily ) |
114 | if ( $wgEchoEnableEmailBatch && $userOptionsLookup->getOption( $user, 'echo-email-frequency' ) > 0 ) { |
115 | // always create a unique event hash for those events don't support bundling |
116 | // this is mainly for group by |
117 | if ( !$bundleHash ) { |
118 | $bundleHash = md5( $event->getType() . '-' . $event->getId() ); |
119 | } |
120 | EmailBatch::addToQueue( $user->getId(), $event->getId(), $priority, $bundleHash ); |
121 | |
122 | return true; |
123 | } |
124 | |
125 | // instant email notification |
126 | $toAddress = MailAddress::newFromUser( $user ); |
127 | $fromAddress = new MailAddress( |
128 | $wgPasswordSender, |
129 | wfMessage( 'emailsender' )->inContentLanguage()->text() |
130 | ); |
131 | $replyAddress = new MailAddress( $wgNoReplyAddress ); |
132 | // Since we are sending a single email, should set the bundle hash to null |
133 | // if it is set with a value from somewhere else |
134 | $event->setBundleHash( null ); |
135 | $email = self::generateEmail( $event, $user ); |
136 | if ( !$email ) { |
137 | return false; |
138 | } |
139 | $subject = $email['subject']; |
140 | $body = $email['body']; |
141 | $options = [ 'replyTo' => $replyAddress ]; |
142 | |
143 | UserMailer::send( $toAddress, $fromAddress, $subject, $body, $options ); |
144 | } |
145 | |
146 | return true; |
147 | } |
148 | |
149 | /** |
150 | * Handler to get bundle rules, handles echo's own events and calls the EchoGetBundleRule hook, |
151 | * which defines the bundle rule for the extensions notification. |
152 | * |
153 | * @param Event $event |
154 | * @param string &$bundleString Determines how the notification should be bundled, for example, |
155 | * talk page notification is bundled based on namespace and title, the bundle string would be |
156 | * 'edit-user-talk-' + namespace + title, email digest/email bundling would use this hash as |
157 | * a key to identify bundle-able event. For web bundling, we bundle further based on user's |
158 | * visit to the overlay, we would generate a display hash based on the hash of $bundleString |
159 | */ |
160 | public static function getBundleRules( $event, &$bundleString ) { |
161 | switch ( $event->getType() ) { |
162 | case 'edit-user-page': |
163 | case 'edit-user-talk': |
164 | case 'page-linked': |
165 | $bundleString = $event->getType(); |
166 | if ( $event->getTitle() ) { |
167 | $bundleString .= '-' . $event->getTitle()->getNamespace() |
168 | . '-' . $event->getTitle()->getDBkey(); |
169 | } |
170 | break; |
171 | case 'mention-success': |
172 | case 'mention-failure': |
173 | $bundleString = 'mention-status-' . $event->getExtraParam( 'revid' ); |
174 | break; |
175 | case 'watchlist-change': |
176 | case 'minor-watchlist-change': |
177 | $bundleString = 'watchlist-change'; |
178 | if ( $event->getTitle() ) { |
179 | $bundleString .= '-' . $event->getTitle()->getNamespace() |
180 | . '-' . $event->getTitle()->getDBkey(); |
181 | } |
182 | break; |
183 | default: |
184 | $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ); |
185 | $hookRunner->onEchoGetBundleRules( $event, $bundleString ); |
186 | } |
187 | } |
188 | |
189 | /** |
190 | * @param Event $event |
191 | * @param User $user |
192 | * @return array|false An array of 'subject' and 'body', or false if things went wrong |
193 | */ |
194 | private static function generateEmail( Event $event, User $user ) { |
195 | $emailFormat = NotifUser::newFromUser( $user )->getEmailFormat(); |
196 | $services = MediaWikiServices::getInstance(); |
197 | $userOptionsLookup = $services->getUserOptionsLookup(); |
198 | $lang = $services->getLanguageFactory() |
199 | ->getLanguage( $userOptionsLookup->getOption( $user, 'language' ) ); |
200 | $formatter = new EchoPlainTextEmailFormatter( $user, $lang ); |
201 | $content = $formatter->format( $event, 'email' ); |
202 | if ( !$content ) { |
203 | return false; |
204 | } |
205 | |
206 | if ( $emailFormat === EmailFormat::HTML ) { |
207 | $htmlEmailFormatter = new EchoHtmlEmailFormatter( $user, $lang ); |
208 | $htmlContent = $htmlEmailFormatter->format( $event, 'email' ); |
209 | $multipartBody = [ |
210 | 'text' => $content['body'], |
211 | 'html' => $htmlContent['body'] |
212 | ]; |
213 | $content['body'] = $multipartBody; |
214 | } |
215 | |
216 | return $content; |
217 | } |
218 | } |
219 | |
220 | class_alias( Notifier::class, 'EchoNotifier' ); |