MediaWiki master
RightsLogFormatter.php
Go to the documentation of this file.
1<?php
12namespace MediaWiki\Logging;
13
19
28 protected function makePageLink( ?Title $title = null, $parameters = [], $html = null ) {
29 $userrightsInterwikiDelimiter = $this->context->getConfig()
31
32 if ( !$this->plaintext ) {
33 $text = $this->getContentLanguage()->
34 ucfirst( $title->getDBkey() );
35 $parts = explode( $userrightsInterwikiDelimiter, $text, 2 );
36
37 if ( count( $parts ) === 2 ) {
38 // @phan-suppress-next-line SecurityCheck-DoubleEscaped
39 $titleLink = WikiMap::foreignUserLink(
40 $parts[1],
41 $parts[0],
42 htmlspecialchars(
43 strtr( $parts[0], '_', ' ' ) .
44 $userrightsInterwikiDelimiter .
45 $parts[1]
46 )
47 );
48
49 if ( $titleLink !== false ) {
50 return $titleLink;
51 }
52 }
53 }
54
55 return parent::makePageLink( $title, $parameters, $title ? $title->getText() : null );
56 }
57
59 protected function getMessageKey() {
60 $key = parent::getMessageKey();
61 $params = $this->getMessageParameters();
62 if ( !isset( $params[3] ) && !isset( $params[4] ) ) {
63 // Messages: logentry-rights-rights-legacy
64 $key .= '-legacy';
65 }
66
67 return $key;
68 }
69
71 protected function getMessageParameters() {
72 $params = parent::getMessageParameters();
73
74 // Really old entries that lack old/new groups,
75 // so don't try to process them
76 if ( !$this->shouldProcessParams( $params ) ) {
77 return $params;
78 }
79
80 // Groups are stored as [ name => expiry|null ]
81 $oldGroups = $this->getOldGroups( $params );
82 $newGroups = $this->getNewGroups( $params );
83
84 // These params were used in the past, when the log message said "from: X to: Y"
85 // They were kept not to break translations
86 if ( count( $oldGroups ) ) {
87 $params[3] = Message::rawParam( $this->formatRightsList( $oldGroups ) );
88 } else {
89 $params[3] = $this->msg( 'rightsnone' )->text();
90 }
91 if ( count( $newGroups ) ) {
92 $params[4] = Message::rawParam( $this->formatRightsList( $newGroups ) );
93 } else {
94 $params[4] = $this->msg( 'rightsnone' )->text();
95 }
96
97 $performerName = $params[1];
98 $userName = $this->entry->getTarget()->getText();
99
100 $params[5] = $userName;
101
102 $groupChanges = $this->classifyGroupChanges( $oldGroups, $newGroups );
103
104 // The following messages are used here:
105 // * logentry-rights-rights-granted
106 // * logentry-rights-rights-revoked
107 // * logentry-rights-rights-expiry-changed
108 // * logentry-rights-rights-kept
109 // * logentry-rights-autopromote-granted
110 // * logentry-rights-autopromote-revoked
111 // * logentry-rights-autopromote-expiry-changed
112 // * logentry-rights-autopromote-kept
113 $messagePrefix = 'logentry-rights-rights-';
114 if ( $this->entry->getSubtype() === 'autopromote' ) {
115 $messagePrefix = 'logentry-rights-autopromote-';
116 }
117
118 $params[6] = $this->formatChangesToGroups( $groupChanges, $performerName, $userName,
119 $messagePrefix );
120
121 return $params;
122 }
123
133 protected function shouldProcessParams( array $params ) {
134 return isset( $params[3] ) || isset( $params[4] );
135 }
136
146 protected function getOldGroups( array $params ) {
147 if ( !isset( $params[3] ) ) {
148 return [];
149 }
150
151 $allParams = $this->entry->getParameters();
152 return $this->joinGroupsWithExpiries( $params[3], $allParams['oldmetadata'] ?? [] );
153 }
154
164 protected function getNewGroups( array $params ) {
165 if ( !isset( $params[4] ) ) {
166 return [];
167 }
168
169 $allParams = $this->entry->getParameters();
170 return $this->joinGroupsWithExpiries( $params[4], $allParams['newmetadata'] ?? [] );
171 }
172
185 protected function joinGroupsWithExpiries( $groupNames, array $metadata ) {
186 $groupNames = $this->makeGroupArray( $groupNames );
187 if ( !$this->plaintext && count( $groupNames ) ) {
188 $this->replaceGroupsWithMemberNames( $groupNames );
189 }
190
191 $expiries = [];
192 foreach (
193 array_map( null, $groupNames, $metadata )
194 as [ $groupName, $groupMetadata ]
195 ) {
196 if ( isset( $groupMetadata['expiry'] ) ) {
197 $expiry = $groupMetadata['expiry'];
198 } else {
199 $expiry = null;
200 }
201 $expiries[$groupName] = $expiry;
202 }
203 return $expiries;
204 }
205
213 protected function replaceGroupsWithMemberNames( array &$groupNames ) {
214 $lang = $this->context->getLanguage();
215 $userName = $this->entry->getTarget()->getText();
216 foreach ( $groupNames as &$group ) {
217 $group = $lang->getGroupMemberName( $group, $userName );
218 }
219 }
220
236 protected function classifyGroupChanges( array $oldGroups, array $newGroups ) {
237 $granted = array_diff_key( $newGroups, $oldGroups );
238 $revoked = array_diff_key( $oldGroups, $newGroups );
239 $kept = array_intersect_key( $oldGroups, $newGroups );
240
241 $expiryChanged = [];
242 $noChange = [];
243
244 foreach ( $kept as $group => $oldExpiry ) {
245 $newExpiry = $newGroups[$group];
246 if ( $oldExpiry !== $newExpiry ) {
247 $expiryChanged[$group] = [ $oldExpiry, $newExpiry ];
248 } else {
249 $noChange[$group] = $oldExpiry;
250 }
251 }
252
253 // These contain both group names and their expiry times
254 // in case of 'expiry-changed', the times are in an array [ old, new ]
255 return [
256 'granted' => $granted,
257 'revoked' => $revoked,
258 'expiry-changed' => $expiryChanged,
259 'kept' => $noChange,
260 ];
261 }
262
273 protected function formatChangesToGroups( array $groupChanges, string $performerName,
274 string $targetName, string $messagePrefix = 'logentry-rights-rights-'
275 ) {
276 $formattedChanges = [];
277
278 foreach ( $groupChanges as $changeType => $groups ) {
279 if ( !count( $groups ) ) {
280 continue;
281 }
282
283 if ( $changeType === 'expiry-changed' ) {
284 $formattedList = $this->formatRightsListExpiryChanged( $groups );
285 } else {
286 $formattedList = $this->formatRightsList( $groups );
287 }
288
289 $formattedChanges[] = $this->msg(
290 $messagePrefix . $changeType,
291 $formattedList,
292 $performerName,
293 $targetName
294 );
295 }
296
297 $uiLanguage = $this->context->getLanguage();
298 return $uiLanguage->semicolonList( $formattedChanges );
299 }
300
301 private function formatRightsListExpiryChanged( array $groups ): string {
302 $list = [];
303
304 foreach ( $groups as $group => [ $oldExpiry, $newExpiry ] ) {
305 $oldExpiryFormatted = $oldExpiry ? $this->formatDate( $oldExpiry ) : false;
306 $newExpiryFormatted = $newExpiry ? $this->formatDate( $newExpiry ) : false;
307
308 if ( $oldExpiryFormatted && $newExpiryFormatted ) {
309 // The expiration was changed
310 $list[] = $this->msg( 'rightslogentry-expiry-changed' )->params(
311 $group,
312 $newExpiryFormatted['whole'],
313 $newExpiryFormatted['date'],
314 $newExpiryFormatted['time'],
315 $oldExpiryFormatted['whole'],
316 $oldExpiryFormatted['date'],
317 $oldExpiryFormatted['time']
318 )->parse();
319 } elseif ( $oldExpiryFormatted ) {
320 // The expiration was removed
321 $list[] = $this->msg( 'rightslogentry-expiry-removed' )->params(
322 $group,
323 $oldExpiryFormatted['whole'],
324 $oldExpiryFormatted['date'],
325 $oldExpiryFormatted['time']
326 )->parse();
327 } elseif ( $newExpiryFormatted ) {
328 // The expiration was added
329 $list[] = $this->msg( 'rightslogentry-expiry-set' )->params(
330 $group,
331 $newExpiryFormatted['whole'],
332 $newExpiryFormatted['date'],
333 $newExpiryFormatted['time']
334 )->parse();
335 } else {
336 // The rights are and were permanent
337 // Shouldn't happen as we process only changes to expiry time here
338 $list[] = htmlspecialchars( $group );
339 }
340 }
341
342 $uiLanguage = $this->context->getLanguage();
343 return $uiLanguage->listToText( $list );
344 }
345
346 private function formatRightsList( array $groups ): string {
347 $uiLanguage = $this->context->getLanguage();
348 // separate arrays of temporary and permanent memberships
349 $tempList = $permList = [];
350
351 foreach ( $groups as $group => $expiry ) {
352 if ( $expiry ) {
353 // format the group and expiry into a friendly string
354 $expiryFormatted = $this->formatDate( $expiry );
355 $tempList[] = $this->msg( 'rightslogentry-temporary-group' )->params( $group,
356 $expiryFormatted['whole'], $expiryFormatted['date'], $expiryFormatted['time'] )
357 ->parse();
358 } else {
359 // the right does not expire; just insert the group name
360 $permList[] = htmlspecialchars( $group );
361 }
362 }
363
364 // place all temporary memberships first, to avoid the ambiguity of
365 // "administrator, bureaucrat and importer (temporary, until X time)"
366 return $uiLanguage->listToText( array_merge( $tempList, $permList ) );
367 }
368
369 private function formatDate( string $date ): array {
370 $uiLanguage = $this->context->getLanguage();
371 $uiUser = $this->context->getUser();
372
373 return [
374 'whole' => $uiLanguage->userTimeAndDate( $date, $uiUser ),
375 'date' => $uiLanguage->userDate( $date, $uiUser ),
376 'time' => $uiLanguage->userTime( $date, $uiUser ),
377 ];
378 }
379
381 protected function getParametersForApi() {
382 $entry = $this->entry;
383 $params = $entry->getParameters();
384
385 static $map = [
386 '4:array:oldgroups',
387 '5:array:newgroups',
388 '4::oldgroups' => '4:array:oldgroups',
389 '5::newgroups' => '5:array:newgroups',
390 ];
391 foreach ( $map as $index => $key ) {
392 if ( isset( $params[$index] ) ) {
393 $params[$key] = $params[$index];
394 unset( $params[$index] );
395 }
396 }
397
398 // Really old entries do not have log params, so form them from whatever info
399 // we have.
400 // Also walk through the parallel arrays of groups and metadata, combining each
401 // metadata array with the name of the group it pertains to
402 if ( isset( $params['4:array:oldgroups'] ) ) {
403 $params['4:array:oldgroups'] = $this->makeGroupArray( $params['4:array:oldgroups'] );
404
405 $oldmetadata =& $params['oldmetadata'];
406 // unset old metadata entry to ensure metadata goes at the end of the params array
407 unset( $params['oldmetadata'] );
408 $params['oldmetadata'] = array_map( static function ( $index ) use ( $params, $oldmetadata ) {
409 $result = [ 'group' => $params['4:array:oldgroups'][$index] ];
410 if ( isset( $oldmetadata[$index] ) ) {
411 $result += $oldmetadata[$index];
412 }
413 $result['expiry'] = ApiResult::formatExpiry( $result['expiry'] ?? null );
414
415 return $result;
416 }, array_keys( $params['4:array:oldgroups'] ) );
417 }
418
419 if ( isset( $params['5:array:newgroups'] ) ) {
420 $params['5:array:newgroups'] = $this->makeGroupArray( $params['5:array:newgroups'] );
421
422 $newmetadata =& $params['newmetadata'];
423 // unset old metadata entry to ensure metadata goes at the end of the params array
424 unset( $params['newmetadata'] );
425 $params['newmetadata'] = array_map( static function ( $index ) use ( $params, $newmetadata ) {
426 $result = [ 'group' => $params['5:array:newgroups'][$index] ];
427 if ( isset( $newmetadata[$index] ) ) {
428 $result += $newmetadata[$index];
429 }
430 $result['expiry'] = ApiResult::formatExpiry( $result['expiry'] ?? null );
431
432 return $result;
433 }, array_keys( $params['5:array:newgroups'] ) );
434 }
435
436 return $params;
437 }
438
440 public function formatParametersForApi() {
441 $ret = parent::formatParametersForApi();
442 if ( isset( $ret['oldgroups'] ) ) {
443 ApiResult::setIndexedTagName( $ret['oldgroups'], 'g' );
444 }
445 if ( isset( $ret['newgroups'] ) ) {
446 ApiResult::setIndexedTagName( $ret['newgroups'], 'g' );
447 }
448 if ( isset( $ret['oldmetadata'] ) ) {
449 ApiResult::setArrayType( $ret['oldmetadata'], 'array' );
450 ApiResult::setIndexedTagName( $ret['oldmetadata'], 'g' );
451 }
452 if ( isset( $ret['newmetadata'] ) ) {
453 ApiResult::setArrayType( $ret['newmetadata'], 'array' );
454 ApiResult::setIndexedTagName( $ret['newmetadata'], 'g' );
455 }
456 return $ret;
457 }
458
462 private function makeGroupArray( $group ): array {
463 // Migrate old group params from string to array
464 if ( $group === '' ) {
465 $group = [];
466 } elseif ( is_string( $group ) ) {
467 $group = array_map( 'trim', explode( ',', $group ) );
468 }
469 return $group;
470 }
471}
472
474class_alias( RightsLogFormatter::class, 'RightsLogFormatter' );
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
This class represents the result of the API operations.
Definition ApiResult.php:34
Implements the default log formatting.
msg( $key,... $params)
Shortcut for wfMessage which honors local context.
This class formats rights log entries.
classifyGroupChanges(array $oldGroups, array $newGroups)
Compares the user groups from before and after this log entry and splits them into four categories: g...
getNewGroups(array $params)
Returns the new groups related to this log entry together with their expiry times.
formatParametersForApi()
Format parameters for API output.The result array should generally map named keys to values....
getParametersForApi()
Get the array of parameters, converted from legacy format if necessary.1.25 to override array
formatChangesToGroups(array $groupChanges, string $performerName, string $targetName, string $messagePrefix='logentry-rights-rights-')
Wraps the changes to user groups into a human-readable messages, so that they can be passed as a para...
getMessageKey()
Returns a key to be used for formatting the action sentence.Default is logentry-TYPE-SUBTYPE for mode...
getOldGroups(array $params)
Returns the old groups related to this log entry together with their expiry times.
getMessageParameters()
Formats parameters intended for action message from array of all parameters.There are three hardcoded...
makePageLink(?Title $title=null, $parameters=[], $html=null)
Helper to make a link to the page, taking the plaintext value in consideration.to override string wik...
replaceGroupsWithMemberNames(array &$groupNames)
Replaces the group names in the array with their localized member names.
joinGroupsWithExpiries( $groupNames, array $metadata)
Joins group names from one array with their expiry times from the another.
shouldProcessParams(array $params)
Checks whether the additional message parameters should be processed.
A class containing constants representing the names of configuration variables.
const UserrightsInterwikiDelimiter
Name constant for the UserrightsInterwikiDelimiter setting, for use with Config::get()
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
Represents a title within MediaWiki.
Definition Title.php:69
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:19
msg( $key,... $params)