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