Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
79.17% |
133 / 168 |
|
44.44% |
4 / 9 |
CRAP | |
0.00% |
0 / 1 |
BlockLogFormatter | |
79.17% |
133 / 168 |
|
44.44% |
4 / 9 |
84.36 | |
0.00% |
0 / 1 |
getMessageParameters | |
88.06% |
59 / 67 |
|
0.00% |
0 / 1 |
17.49 | |||
extractParameters | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
getPreloadTitles | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
5.27 | |||
getActionLinks | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
30 | |||
formatBlockFlags | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
3.01 | |||
formatBlockFlag | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getParametersForApi | |
82.76% |
24 / 29 |
|
0.00% |
0 / 1 |
10.51 | |||
formatParametersForApi | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
getMessageKey | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 |
1 | <?php |
2 | /** |
3 | * Formatter for block log entries. |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @license GPL-2.0-or-later |
22 | * @since 1.25 |
23 | */ |
24 | |
25 | use MediaWiki\Linker\Linker; |
26 | use MediaWiki\MainConfigNames; |
27 | use MediaWiki\SpecialPage\SpecialPage; |
28 | use MediaWiki\Title\Title; |
29 | use MediaWiki\User\User; |
30 | |
31 | /** |
32 | * This class formats block log entries. |
33 | * |
34 | * @since 1.25 |
35 | */ |
36 | class BlockLogFormatter extends LogFormatter { |
37 | protected function getMessageParameters() { |
38 | $params = parent::getMessageParameters(); |
39 | |
40 | $title = $this->entry->getTarget(); |
41 | if ( substr( $title->getText(), 0, 1 ) === '#' ) { |
42 | // autoblock - no user link possible |
43 | $params[2] = $title->getText(); |
44 | $params[3] = ''; // no user name for gender use |
45 | } else { |
46 | // Create a user link for the blocked |
47 | $username = $title->getText(); |
48 | // @todo Store the user identifier in the parameters |
49 | // to make this faster for future log entries |
50 | $targetUser = User::newFromName( $username, false ); |
51 | $params[2] = Message::rawParam( $this->makeUserLink( $targetUser, Linker::TOOL_LINKS_NOBLOCK ) ); |
52 | $params[3] = $username; // plain user name for gender use |
53 | } |
54 | |
55 | $subtype = $this->entry->getSubtype(); |
56 | if ( $subtype === 'block' || $subtype === 'reblock' ) { |
57 | if ( !isset( $params[4] ) ) { |
58 | // Very old log entry without duration: means infinity |
59 | $params[4] = 'infinity'; |
60 | } |
61 | // Localize the duration, and add a tooltip |
62 | // in English to help visitors from other wikis. |
63 | // The lrm is needed to make sure that the number |
64 | // is shown on the correct side of the tooltip text. |
65 | // @phan-suppress-next-line SecurityCheck-DoubleEscaped |
66 | $durationTooltip = '‎' . htmlspecialchars( $params[4] ); |
67 | $blockExpiry = $this->context->getLanguage()->translateBlockExpiry( |
68 | $params[4], |
69 | $this->context->getUser(), |
70 | (int)wfTimestamp( TS_UNIX, $this->entry->getTimestamp() ) |
71 | ); |
72 | if ( $this->plaintext ) { |
73 | // @phan-suppress-next-line SecurityCheck-XSS Unlikely positive, only if language format is bad |
74 | $params[4] = Message::rawParam( $blockExpiry ); |
75 | } else { |
76 | $params[4] = Message::rawParam( |
77 | "<span class=\"blockExpiry\" title=\"$durationTooltip\">" . |
78 | // @phan-suppress-next-line SecurityCheck-DoubleEscaped language class does not escape |
79 | htmlspecialchars( $blockExpiry ) . |
80 | '</span>' |
81 | ); |
82 | } |
83 | $params[5] = isset( $params[5] ) ? |
84 | self::formatBlockFlags( $params[5], $this->context->getLanguage() ) : ''; |
85 | |
86 | // block restrictions |
87 | if ( isset( $params[6] ) ) { |
88 | $pages = $params[6]['pages'] ?? []; |
89 | $pageLinks = []; |
90 | foreach ( $pages as $page ) { |
91 | $pageLinks[] = $this->makePageLink( Title::newFromText( $page ) ); |
92 | } |
93 | |
94 | $rawNamespaces = $params[6]['namespaces'] ?? []; |
95 | $namespaces = []; |
96 | foreach ( $rawNamespaces as $ns ) { |
97 | $text = (int)$ns === NS_MAIN |
98 | ? $this->msg( 'blanknamespace' )->escaped() |
99 | : htmlspecialchars( $this->context->getLanguage()->getFormattedNsText( $ns ) ); |
100 | if ( $this->plaintext ) { |
101 | // Because the plaintext cannot link to the Special:AllPages |
102 | // link that is linked to in non-plaintext mode, just return |
103 | // the name of the namespace. |
104 | $namespaces[] = $text; |
105 | } else { |
106 | $namespaces[] = $this->makePageLink( |
107 | SpecialPage::getTitleFor( 'Allpages' ), |
108 | [ 'namespace' => $ns ], |
109 | $text |
110 | ); |
111 | } |
112 | } |
113 | |
114 | $rawActions = $params[6]['actions'] ?? []; |
115 | $actions = []; |
116 | foreach ( $rawActions as $action ) { |
117 | $actions[] = $this->msg( 'ipb-action-' . $action )->escaped(); |
118 | } |
119 | |
120 | $restrictions = []; |
121 | if ( $pageLinks ) { |
122 | $restrictions[] = $this->msg( 'logentry-partialblock-block-page' ) |
123 | ->numParams( count( $pageLinks ) ) |
124 | ->rawParams( $this->context->getLanguage()->listToText( $pageLinks ) )->escaped(); |
125 | } |
126 | |
127 | if ( $namespaces ) { |
128 | $restrictions[] = $this->msg( 'logentry-partialblock-block-ns' ) |
129 | ->numParams( count( $namespaces ) ) |
130 | ->rawParams( $this->context->getLanguage()->listToText( $namespaces ) )->escaped(); |
131 | } |
132 | $enablePartialActionBlocks = $this->context->getConfig() |
133 | ->get( MainConfigNames::EnablePartialActionBlocks ); |
134 | if ( $actions && $enablePartialActionBlocks ) { |
135 | $restrictions[] = $this->msg( 'logentry-partialblock-block-action' ) |
136 | ->numParams( count( $actions ) ) |
137 | ->rawParams( $this->context->getLanguage()->listToText( $actions ) )->escaped(); |
138 | } |
139 | |
140 | $params[6] = Message::rawParam( $this->context->getLanguage()->listToText( $restrictions ) ); |
141 | } |
142 | } |
143 | |
144 | return $params; |
145 | } |
146 | |
147 | protected function extractParameters() { |
148 | $params = parent::extractParameters(); |
149 | // Legacy log params returning the params in index 3 and 4, moved to 4 and 5 |
150 | if ( $this->entry->isLegacy() && isset( $params[3] ) ) { |
151 | if ( isset( $params[4] ) ) { |
152 | $params[5] = $params[4]; |
153 | } |
154 | $params[4] = $params[3]; |
155 | $params[3] = ''; |
156 | } |
157 | return $params; |
158 | } |
159 | |
160 | public function getPreloadTitles() { |
161 | $title = $this->entry->getTarget(); |
162 | $preload = []; |
163 | // Preload user page for non-autoblocks |
164 | if ( substr( $title->getText(), 0, 1 ) !== '#' && $title->canExist() ) { |
165 | $preload[] = $title->getTalkPage(); |
166 | } |
167 | // Preload page restriction |
168 | $params = $this->extractParameters(); |
169 | if ( isset( $params[6]['pages'] ) ) { |
170 | foreach ( $params[6]['pages'] as $page ) { |
171 | $preload[] = Title::newFromText( $page ); |
172 | } |
173 | } |
174 | return $preload; |
175 | } |
176 | |
177 | public function getActionLinks() { |
178 | $subtype = $this->entry->getSubtype(); |
179 | $linkRenderer = $this->getLinkRenderer(); |
180 | if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden |
181 | || !( $subtype === 'block' || $subtype === 'reblock' ) |
182 | || !$this->context->getAuthority()->isAllowed( 'block' ) |
183 | ) { |
184 | return ''; |
185 | } |
186 | |
187 | // Show unblock/change block link |
188 | $title = $this->entry->getTarget(); |
189 | $links = [ |
190 | $linkRenderer->makeKnownLink( |
191 | SpecialPage::getTitleFor( 'Unblock', $title->getDBkey() ), |
192 | $this->msg( 'unblocklink' )->text() |
193 | ), |
194 | $linkRenderer->makeKnownLink( |
195 | SpecialPage::getTitleFor( 'Block', $title->getDBkey() ), |
196 | $this->msg( 'change-blocklink' )->text() |
197 | ) |
198 | ]; |
199 | |
200 | return $this->msg( 'parentheses' )->rawParams( |
201 | $this->context->getLanguage()->pipeList( $links ) )->escaped(); |
202 | } |
203 | |
204 | /** |
205 | * Convert a comma-delimited list of block log flags |
206 | * into a more readable (and translated) form |
207 | * |
208 | * @param string $flags Flags to format |
209 | * @param Language $lang |
210 | * @return string |
211 | */ |
212 | public static function formatBlockFlags( $flags, Language $lang ) { |
213 | $flags = trim( $flags ); |
214 | if ( $flags === '' ) { |
215 | return ''; // nothing to do |
216 | } |
217 | $flags = explode( ',', $flags ); |
218 | $flagsCount = count( $flags ); |
219 | |
220 | for ( $i = 0; $i < $flagsCount; $i++ ) { |
221 | $flags[$i] = self::formatBlockFlag( $flags[$i], $lang ); |
222 | } |
223 | |
224 | return wfMessage( 'parentheses' )->inLanguage( $lang ) |
225 | ->rawParams( $lang->commaList( $flags ) )->escaped(); |
226 | } |
227 | |
228 | /** |
229 | * Translate a block log flag if possible |
230 | * |
231 | * @param string $flag Flag to translate |
232 | * @param Language $lang Language object to use |
233 | * @return string |
234 | */ |
235 | public static function formatBlockFlag( $flag, Language $lang ) { |
236 | static $messages = []; |
237 | |
238 | if ( !isset( $messages[$flag] ) ) { |
239 | $messages[$flag] = htmlspecialchars( $flag ); // Fallback |
240 | |
241 | // For grepping. The following core messages can be used here: |
242 | // * block-log-flags-angry-autoblock |
243 | // * block-log-flags-anononly |
244 | // * block-log-flags-hiddenname |
245 | // * block-log-flags-noautoblock |
246 | // * block-log-flags-nocreate |
247 | // * block-log-flags-noemail |
248 | // * block-log-flags-nousertalk |
249 | $msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang ); |
250 | |
251 | if ( $msg->exists() ) { |
252 | $messages[$flag] = $msg->escaped(); |
253 | } |
254 | } |
255 | |
256 | return $messages[$flag]; |
257 | } |
258 | |
259 | protected function getParametersForApi() { |
260 | $entry = $this->entry; |
261 | $params = $entry->getParameters(); |
262 | |
263 | static $map = [ |
264 | // While this looks wrong to be starting at 5 rather than 4, it's |
265 | // because getMessageParameters uses $4 for its own purposes. |
266 | '5::duration', |
267 | '6:array:flags', |
268 | '6::flags' => '6:array:flags', |
269 | ]; |
270 | |
271 | foreach ( $map as $index => $key ) { |
272 | if ( isset( $params[$index] ) ) { |
273 | $params[$key] = $params[$index]; |
274 | unset( $params[$index] ); |
275 | } |
276 | } |
277 | |
278 | ksort( $params ); |
279 | |
280 | $subtype = $entry->getSubtype(); |
281 | if ( $subtype === 'block' || $subtype === 'reblock' ) { |
282 | // Defaults for old log entries missing some fields |
283 | $params += [ |
284 | '5::duration' => 'infinity', |
285 | '6:array:flags' => [], |
286 | ]; |
287 | |
288 | if ( !is_array( $params['6:array:flags'] ) ) { |
289 | // @phan-suppress-next-line PhanSuspiciousValueComparison |
290 | $params['6:array:flags'] = $params['6:array:flags'] === '' |
291 | ? [] |
292 | : explode( ',', $params['6:array:flags'] ); |
293 | } |
294 | |
295 | if ( wfIsInfinity( $params['5::duration'] ) ) { |
296 | // Normalize all possible values to one for pre-T241709 rows |
297 | $params['5::duration'] = 'infinity'; |
298 | } else { |
299 | $ts = (int)wfTimestamp( TS_UNIX, $entry->getTimestamp() ); |
300 | $expiry = strtotime( $params['5::duration'], $ts ); |
301 | if ( $expiry !== false && $expiry > 0 ) { |
302 | $params[':timestamp:expiry'] = $expiry; |
303 | } |
304 | } |
305 | } |
306 | |
307 | return $params; |
308 | } |
309 | |
310 | /** |
311 | * @inheritDoc |
312 | * @suppress PhanTypeInvalidDimOffset |
313 | */ |
314 | public function formatParametersForApi() { |
315 | $ret = parent::formatParametersForApi(); |
316 | if ( isset( $ret['flags'] ) ) { |
317 | ApiResult::setIndexedTagName( $ret['flags'], 'f' ); |
318 | } |
319 | |
320 | if ( isset( $ret['restrictions']['pages'] ) ) { |
321 | $ret['restrictions']['pages'] = array_map( function ( $title ) { |
322 | return $this->formatParameterValueForApi( 'page', 'title-link', $title ); |
323 | }, $ret['restrictions']['pages'] ); |
324 | ApiResult::setIndexedTagName( $ret['restrictions']['pages'], 'p' ); |
325 | } |
326 | |
327 | if ( isset( $ret['restrictions']['namespaces'] ) ) { |
328 | // @phan-suppress-next-line PhanTypeMismatchArgument False positive |
329 | ApiResult::setIndexedTagName( $ret['restrictions']['namespaces'], 'ns' ); |
330 | } |
331 | |
332 | return $ret; |
333 | } |
334 | |
335 | protected function getMessageKey() { |
336 | $type = $this->entry->getType(); |
337 | $subtype = $this->entry->getSubtype(); |
338 | $sitewide = $this->entry->getParameters()['sitewide'] ?? true; |
339 | |
340 | $key = "logentry-$type-$subtype"; |
341 | if ( ( $subtype === 'block' || $subtype === 'reblock' ) && !$sitewide ) { |
342 | // $this->getMessageParameters is doing too much. We just need |
343 | // to check the presence of restrictions ($param[6]) and calling |
344 | // on parent gives us that |
345 | $params = parent::getMessageParameters(); |
346 | |
347 | // message changes depending on whether there are editing restrictions or not |
348 | if ( isset( $params[6] ) ) { |
349 | $key = "logentry-partial$type-$subtype"; |
350 | } else { |
351 | $key = "logentry-non-editing-$type-$subtype"; |
352 | } |
353 | } |
354 | |
355 | return $key; |
356 | } |
357 | } |