Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 120 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
TranslationNotifyUser | |
0.00% |
0 / 120 |
|
0.00% |
0 / 8 |
210 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
leaveUserMessage | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
2 | |||
sendTranslationNotificationEmail | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
2 | |||
getRelevantLanguages | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
getUserLanguageOption | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getUserFirstLanguage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUserLanguages | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getUrlProtocol | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\TranslationNotifications\Utilities; |
5 | |
6 | use MediaWiki\Extension\TranslationNotifications\Jobs\TranslationNotificationsEmailJob; |
7 | use MediaWiki\MassMessage\Job\MassMessageJob; |
8 | use MediaWiki\MediaWikiServices; |
9 | use MediaWiki\Message\Message; |
10 | use MediaWiki\SpecialPage\SpecialPage; |
11 | use MediaWiki\Title\Title; |
12 | use MediaWiki\User\User; |
13 | use MediaWiki\User\UserIdentity; |
14 | use MediaWiki\WikiMap\WikiMap; |
15 | |
16 | /** |
17 | * Encapsulates the logic needed to create a notification to be sent to Users based on the |
18 | * type of notification they want. Creates the necessary job classes that are then used to |
19 | * actually deliver the notification. |
20 | * @license GPL-2.0-or-later |
21 | */ |
22 | class TranslationNotifyUser { |
23 | private Title $translatablePageTitle; |
24 | private User $notifier; |
25 | private string $noReplyAddress; |
26 | /** @var string[] */ |
27 | private array $localInterwikis; |
28 | private bool $httpsInEmail; |
29 | |
30 | // Request information |
31 | /** Request priority: `unset`, `high`, `medium` or `low` */ |
32 | private string $priority; |
33 | private string $deadline; |
34 | private string $notificationText; |
35 | /** |
36 | * A list of languages for which the translators are to be notified. Empty for all languages. |
37 | * @var string[] |
38 | */ |
39 | private array $languagesToNotify; |
40 | |
41 | /** |
42 | * @param Title $translatablePageTitle |
43 | * @param User $notifier |
44 | * @param string[] $localInterwikis |
45 | * @param string $noReplyAddress |
46 | * @param bool $httpsInEmail |
47 | * @param array $requestData |
48 | */ |
49 | public function __construct( |
50 | Title $translatablePageTitle, |
51 | User $notifier, |
52 | array $localInterwikis, |
53 | string $noReplyAddress, |
54 | bool $httpsInEmail, |
55 | array $requestData |
56 | ) { |
57 | $this->notifier = $notifier; |
58 | $this->translatablePageTitle = $translatablePageTitle; |
59 | |
60 | $this->noReplyAddress = $noReplyAddress; |
61 | $this->localInterwikis = $localInterwikis; |
62 | $this->httpsInEmail = $httpsInEmail; |
63 | |
64 | $this->notificationText = $requestData['text']; |
65 | $this->languagesToNotify = $requestData['languagesToNotify']; |
66 | $this->priority = $requestData['priority'] ?? ''; |
67 | $this->deadline = $requestData['deadline'] ?? ''; |
68 | } |
69 | |
70 | /** |
71 | * Leave a message on the user's talk page. |
72 | * @param User $translator To whom the message to be sent |
73 | * @param string $destination Whether to send it to a talk page on this wiki |
74 | * ('talkpageHere', default) or another one ('talkpageInOtherWiki'). |
75 | * @return MassMessageJob |
76 | */ |
77 | public function leaveUserMessage( |
78 | User $translator, |
79 | string $destination = 'talkpageHere' |
80 | ): MassMessageJob { |
81 | $relevantLanguages = $this->getRelevantLanguages( $translator, $this->languagesToNotify ); |
82 | $userFirstLanguageCode = $this->getUserFirstLanguage( $translator ); |
83 | $userFirstLanguage = MediaWikiServices::getInstance()->getLanguageFactory() |
84 | ->getLanguage( $userFirstLanguageCode ); |
85 | |
86 | $text = wfMessage( |
87 | 'translationnotifications-talkpage-body', |
88 | $translator->getName(), |
89 | NotificationMessageBuilder::getUserName( $translator ), |
90 | $userFirstLanguage->listToText( array_values( $relevantLanguages ) ), |
91 | NotificationMessageBuilder::getMessageTitle( |
92 | $this->translatablePageTitle, $destination, $this->localInterwikis |
93 | ), |
94 | NotificationMessageBuilder::getTranslationURLs( |
95 | $this->translatablePageTitle, $relevantLanguages, 'talkpage', |
96 | $userFirstLanguage, $this->getUrlProtocol() |
97 | ), |
98 | NotificationMessageBuilder::getPriorityClause( $userFirstLanguage, $this->priority ), |
99 | NotificationMessageBuilder::getDeadlineClause( $userFirstLanguage, $this->deadline ), |
100 | NotificationMessageBuilder::getNotificationMessage( |
101 | MediaWikiServices::getInstance()->getContentLanguage(), |
102 | $this->notificationText |
103 | ) |
104 | )->numParams( count( $relevantLanguages ) ) // $9 |
105 | ->params( NotificationMessageBuilder::getSignupURL( $this->getUrlProtocol() ) ) // $10 |
106 | ->inLanguage( $userFirstLanguage ) |
107 | ->text(); |
108 | |
109 | // Bidi-isolation of site name from date |
110 | $text .= $userFirstLanguage->getDirMark() . |
111 | ', ~~~~~'; // Date and time |
112 | |
113 | // Note: Maybe this was originally meant for edit summary, but it's actually used as subject |
114 | $subject = wfMessage( |
115 | 'translationnotifications-edit-summary', |
116 | $this->translatablePageTitle |
117 | )->inLanguage( $userFirstLanguage )->text(); |
118 | |
119 | $listUrl = SpecialPage::getTitleFor( 'NotifyTranslators' )->getCanonicalURL(); |
120 | |
121 | $params = [ |
122 | // This is not the edit summary, but rather hidden comment left after the message |
123 | 'comment' => [ |
124 | $this->notifier->getName(), |
125 | WikiMap::getCurrentWikiId(), |
126 | $listUrl |
127 | ], |
128 | 'message' => $text, |
129 | 'subject' => $subject, |
130 | // Use canonical version of the namespace that works in all wikis and assume that |
131 | // user names are global across wikis |
132 | 'title' => 'User_talk:' . $translator->getName(), |
133 | ]; |
134 | |
135 | // Ignored, the page to deliver to is read from $params['title'] |
136 | $jobTitle = $translator->getTalkPage(); |
137 | |
138 | return new MassMessageJob( $jobTitle, $params ); |
139 | } |
140 | |
141 | /** |
142 | * Notify a user by email. |
143 | * @param User $translator User to whom the email is being sent |
144 | * @return TranslationNotificationsEmailJob |
145 | */ |
146 | public function sendTranslationNotificationEmail( |
147 | User $translator |
148 | ): TranslationNotificationsEmailJob { |
149 | $relevantLanguages = $this->getRelevantLanguages( $translator, $this->languagesToNotify ); |
150 | $userFirstLanguage = MediaWikiServices::getInstance()->getLanguageFactory() |
151 | ->getLanguage( $this->getUserFirstLanguage( $translator ) ); |
152 | $emailSubject = NotificationMessageBuilder::getNotificationSubject( |
153 | $this->translatablePageTitle, $userFirstLanguage |
154 | ); |
155 | |
156 | $translationUrls = NotificationMessageBuilder::getTranslationURLs( |
157 | $this->translatablePageTitle, |
158 | $relevantLanguages, |
159 | 'email', |
160 | $userFirstLanguage, |
161 | $this->getUrlProtocol() |
162 | ); |
163 | |
164 | $emailBody = wfMessage( 'translationnotifications-email-body' ) |
165 | ->params( |
166 | NotificationMessageBuilder::getUserName( $translator ), // $1 |
167 | $userFirstLanguage->listToText( array_values( $relevantLanguages ) ), |
168 | $this->translatablePageTitle, |
169 | $translationUrls, // $4 |
170 | NotificationMessageBuilder::getPriorityClause( $userFirstLanguage, $this->priority ), |
171 | NotificationMessageBuilder::getDeadlineClause( $userFirstLanguage, $this->deadline ), |
172 | $this->notificationText, // $7 |
173 | NotificationMessageBuilder::getSignupURL( $this->getUrlProtocol() ), |
174 | Message::numParam( count( $relevantLanguages ) ), |
175 | $translator->getName() // $10 |
176 | )->inLanguage( $userFirstLanguage )->text(); |
177 | |
178 | $sender = $this->notifier; |
179 | |
180 | // Do not publish the sender's email, but include his/her name |
181 | $emailFrom = TranslationNotificationsEmailJob::buildAddress( |
182 | $this->noReplyAddress, |
183 | $sender->getName(), |
184 | $sender->getRealName() |
185 | ); |
186 | |
187 | $params = [ |
188 | 'to' => TranslationNotificationsEmailJob::addressFromUser( $translator ), |
189 | 'from' => $emailFrom, |
190 | 'body' => $emailBody, |
191 | 'subject' => $emailSubject, |
192 | 'replyTo' => $emailFrom, |
193 | ]; |
194 | |
195 | return new TranslationNotificationsEmailJob( $this->translatablePageTitle, $params ); |
196 | } |
197 | |
198 | /** |
199 | * Returns a list of language codes and names for the current |
200 | * notification to the user. |
201 | * @param User $user User to whom the email is being sent |
202 | * @param string[] $languagesToNotify A list of languages that are notified. |
203 | * Empty for all languages. |
204 | * @return string[] Array of language codes |
205 | */ |
206 | private function getRelevantLanguages( User $user, array $languagesToNotify ): array { |
207 | $userLanguages = $this->getUserLanguages( $user ); |
208 | $userFirstLanguageCode = $userLanguages[0]; |
209 | $limitLanguages = count( $languagesToNotify ); |
210 | $userLanguageNames = []; |
211 | |
212 | $languageNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils(); |
213 | foreach ( $userLanguages as $langCode ) { |
214 | // Don't add this language if particular languages were |
215 | // specified and this language was not one of them. |
216 | if ( ( $limitLanguages && !in_array( $langCode, $languagesToNotify ) ) ) { |
217 | continue; |
218 | } |
219 | |
220 | $userLanguageNames[$langCode] = $languageNameUtils->getLanguageName( |
221 | $langCode, |
222 | $userFirstLanguageCode |
223 | ); |
224 | } |
225 | |
226 | return $userLanguageNames; |
227 | } |
228 | |
229 | /** |
230 | * Returns a language that a user signed up for in |
231 | * Special:TranslatorSignup. |
232 | * @param UserIdentity $user |
233 | * @param int $langNum Number of language. |
234 | * @return string Language code, or null if it wasn't defined. |
235 | */ |
236 | private function getUserLanguageOption( UserIdentity $user, int $langNum ): string { |
237 | return MediaWikiServices::getInstance() |
238 | ->getUserOptionsLookup() |
239 | ->getOption( $user, "translationnotifications-lang-$langNum" ); |
240 | } |
241 | |
242 | /** |
243 | * Returns the code of the first language to which a user signed up in |
244 | * Special:TranslatorSignup. |
245 | * @return string Language code. |
246 | */ |
247 | private function getUserFirstLanguage( User $user ): string { |
248 | return $this->getUserLanguageOption( $user, 1 ); |
249 | } |
250 | |
251 | /** |
252 | * Returns an array of all language codes to which a user signed up in |
253 | * Special:TranslatorSignup. |
254 | * @return array of language codes. |
255 | */ |
256 | private function getUserLanguages( User $user ): array { |
257 | $userLanguages = []; |
258 | |
259 | foreach ( range( 1, 3 ) as $langNum ) { |
260 | $nextLanguage = $this->getUserLanguageOption( $user, $langNum ); |
261 | if ( $nextLanguage !== '' ) { |
262 | $userLanguages[] = $nextLanguage; |
263 | } |
264 | } |
265 | |
266 | return $userLanguages; |
267 | } |
268 | |
269 | /** |
270 | * Returns the URL protocol to be used based on configuration |
271 | * @return string|int a PROTO_* constant |
272 | */ |
273 | private function getUrlProtocol() { |
274 | return !$this->httpsInEmail |
275 | ? PROTO_CANONICAL |
276 | : PROTO_HTTPS; |
277 | } |
278 | } |