1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
|
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Block;
use MediaWiki\Api\ApiBlockInfoHelper;
use MediaWiki\Api\ApiMessage;
use MediaWiki\CommentStore\CommentStoreComment;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\HookContainer\HookRunner;
use MediaWiki\Language\Language;
use MediaWiki\Language\LocalizationContext;
use MediaWiki\Languages\LanguageFactory;
use MediaWiki\Message\Message;
use MediaWiki\Page\PageReferenceValue;
use MediaWiki\Title\TitleFormatter;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityUtils;
/**
* A service class for getting formatted information about a block.
* To obtain an instance, use MediaWikiServices::getInstance()->getBlockErrorFormatter().
*
* @since 1.35
*/
class BlockErrorFormatter {
private TitleFormatter $titleFormatter;
private HookRunner $hookRunner;
private UserIdentityUtils $userIdentityUtils;
private LocalizationContext $uiContext;
private LanguageFactory $languageFactory;
public function __construct(
TitleFormatter $titleFormatter,
HookContainer $hookContainer,
UserIdentityUtils $userIdentityUtils,
LanguageFactory $languageFactory,
LocalizationContext $uiContext
) {
$this->titleFormatter = $titleFormatter;
$this->hookRunner = new HookRunner( $hookContainer );
$this->userIdentityUtils = $userIdentityUtils;
$this->languageFactory = $languageFactory;
$this->uiContext = $uiContext;
}
private function getLanguage(): Language {
return $this->languageFactory->getLanguage( $this->uiContext->getLanguageCode() );
}
/**
* Get a block error message. Different message keys are chosen depending on the
* block features. Message parameters are formatted for the specified user and
* language. The message includes machine-readable data for API error responses.
*
* If passed a CompositeBlock, will get a generic message stating that there are
* multiple blocks. To get all the block messages, use getMessages instead.
*
* @param Block $block
* @param UserIdentity $user
* @param mixed $language Unused since 1.42
* @param string $ip
* @return ApiMessage
*/
public function getMessage(
Block $block,
UserIdentity $user,
$language,
string $ip
): Message {
$key = $this->getBlockErrorMessageKey( $block, $user );
$params = $this->getBlockErrorMessageParams( $block, $user, $ip );
$apiHelper = new ApiBlockInfoHelper;
// @phan-suppress-next-line PhanTypeMismatchReturnSuperType
return ApiMessage::create(
$this->uiContext->msg( $key, $params ),
$apiHelper->getBlockCode( $block ),
[ 'blockinfo' => $apiHelper->getBlockDetails( $block, $this->getLanguage(), $user ) ]
);
}
/**
* Get block error messages for all of the blocks that apply to a user.
*
* @since 1.42
* @param Block $block
* @param UserIdentity $user
* @param string $ip
* @return Message[]
*/
public function getMessages(
Block $block,
UserIdentity $user,
string $ip
): array {
$messages = [];
foreach ( $block->toArray() as $singleBlock ) {
$messages[] = $this->getMessage( $singleBlock, $user, null, $ip );
}
return $messages;
}
/**
* Get a standard set of block details for building a block error message.
*
* @param Block $block
* @return mixed[]
* - identifier: Information for looking up the block
* - targetName: The target, as a string
* - blockerName: The blocker, as a string
* - reason: Reason for the block
* - expiry: Expiry time
* - timestamp: Time the block was created
*/
private function getBlockErrorInfo( Block $block ) {
$blocker = $block->getBlocker();
return [
'identifier' => $block->getIdentifier(),
'targetName' => $block->getTargetName(),
'blockerName' => $blocker ? $blocker->getName() : '',
'reason' => $block->getReasonComment(),
'expiry' => $block->getExpiry(),
'timestamp' => $block->getTimestamp(),
];
}
/**
* Get a standard set of block details for building a block error message,
* formatted for a specified user and language.
*
* @since 1.35
* @param Block $block
* @param UserIdentity $user
* @return mixed[] See getBlockErrorInfo
*/
private function getFormattedBlockErrorInfo(
Block $block,
UserIdentity $user
) {
$info = $this->getBlockErrorInfo( $block );
$language = $this->getLanguage();
$info['expiry'] = $language->formatExpiry( $info['expiry'], true, 'infinity', $user );
$info['timestamp'] = $language->userTimeAndDate( $info['timestamp'], $user );
$info['blockerName'] = $language->embedBidi( $info['blockerName'] );
$info['targetName'] = $language->embedBidi( $info['targetName'] );
$info['reason'] = $this->formatBlockReason( $info['reason'], $language );
return $info;
}
/**
* Format the block reason as plain wikitext in the specified language.
*
* @param CommentStoreComment $reason
* @param Language $language
* @return string
*/
private function formatBlockReason( CommentStoreComment $reason, Language $language ) {
if ( $reason->text === '' ) {
$message = new Message( 'blockednoreason', [], $language );
return $message->plain();
}
return $reason->message->inLanguage( $language )->plain();
}
/**
* Create a link to the blocker's user page. This must be done here rather than in
* the message translation, because the blocker may not be a local user, in which
* case their page cannot be linked.
*
* @param ?UserIdentity $blocker
* @return string Link to the blocker's page; blocker's name if not a local user
*/
private function formatBlockerLink( ?UserIdentity $blocker ) {
if ( !$blocker ) {
// TODO should we say something? This is just matching the code before
// the refactoring in late July 2021
return '';
}
$language = $this->getLanguage();
if ( $blocker->getId() === 0 ) {
// Foreign user
// TODO what about blocks placed by IPs? Shouldn't we check based on
// $blocker's wiki instead? This is just matching the code before the
// refactoring in late July 2021.
return $language->embedBidi( $blocker->getName() );
}
$blockerUserpage = PageReferenceValue::localReference( NS_USER, $blocker->getName() );
$blockerText = $language->embedBidi(
$this->titleFormatter->getText( $blockerUserpage )
);
$prefixedText = $this->titleFormatter->getPrefixedText( $blockerUserpage );
return "[[{$prefixedText}|{$blockerText}]]";
}
/**
* Determine the block error message key by examining the block.
*
* @param Block $block
* @param UserIdentity $user
* @return string Message key
*/
private function getBlockErrorMessageKey( Block $block, UserIdentity $user ) {
$isTempUser = $this->userIdentityUtils->isTemp( $user );
$key = $isTempUser ? 'blockedtext-tempuser' : 'blockedtext';
if ( $block instanceof DatabaseBlock ) {
if ( $block->getType() === Block::TYPE_AUTO ) {
$key = $isTempUser ? 'autoblockedtext-tempuser' : 'autoblockedtext';
} elseif ( !$block->isSitewide() ) {
$key = 'blockedtext-partial';
}
} elseif ( $block instanceof SystemBlock ) {
$key = 'systemblockedtext';
} elseif ( $block instanceof CompositeBlock ) {
$key = 'blockedtext-composite';
}
// Allow extensions to modify the block error message
$this->hookRunner->onGetBlockErrorMessageKey( $block, $key );
return $key;
}
/**
* Get the formatted parameters needed to build the block error messages handled by
* getBlockErrorMessageKey.
*
* @param Block $block
* @param UserIdentity $user
* @param string $ip
* @return mixed[] Params used by standard block error messages, in order:
* - blockerLink: Link to the blocker's user page, if any; otherwise same as blockerName
* - reason: Reason for the block
* - ip: IP address of the user attempting to perform an action
* - blockerName: The blocker, as a bidi-embedded string
* - identifier: Information for looking up the block
* - expiry: Expiry time, in the specified language
* - targetName: The target, as a bidi-embedded string
* - timestamp: Time the block was created, in the specified language
*/
private function getBlockErrorMessageParams(
Block $block,
UserIdentity $user,
string $ip
) {
$info = $this->getFormattedBlockErrorInfo( $block, $user );
// Add params that are specific to the standard block errors
$info['ip'] = $ip;
$info['blockerLink'] = $this->formatBlockerLink( $block->getBlocker() );
// Display the CompositeBlock identifier as a message containing relevant block IDs
if ( $block instanceof CompositeBlock ) {
$ids = $this->getLanguage()->commaList( array_map(
static function ( $id ) {
return '#' . $id;
},
array_filter( $info['identifier'], 'is_int' )
) );
if ( $ids === '' ) {
$idsMsg = $this->uiContext->msg( 'blockedtext-composite-no-ids', [] );
} else {
$idsMsg = $this->uiContext->msg( 'blockedtext-composite-ids', [ $ids ] );
}
$info['identifier'] = $idsMsg->plain();
}
// Messages expect the params in this order
$order = [
'blockerLink',
'reason',
'ip',
'blockerName',
'identifier',
'expiry',
'targetName',
'timestamp',
];
$params = [];
foreach ( $order as $item ) {
$params[] = $info[$item];
}
return $params;
}
}
|