MediaWiki master
RightsLogFormatter.php
Go to the documentation of this file.
1<?php
26namespace MediaWiki\Logging;
27
33
41 protected function makePageLink( ?Title $title = null, $parameters = [], $html = null ) {
42 $userrightsInterwikiDelimiter = $this->context->getConfig()
44
45 if ( !$this->plaintext ) {
46 $text = $this->getContentLanguage()->
47 ucfirst( $title->getDBkey() );
48 $parts = explode( $userrightsInterwikiDelimiter, $text, 2 );
49
50 if ( count( $parts ) === 2 ) {
51 // @phan-suppress-next-line SecurityCheck-DoubleEscaped
52 $titleLink = WikiMap::foreignUserLink(
53 $parts[1],
54 $parts[0],
55 htmlspecialchars(
56 strtr( $parts[0], '_', ' ' ) .
57 $userrightsInterwikiDelimiter .
58 $parts[1]
59 )
60 );
61
62 if ( $titleLink !== false ) {
63 return $titleLink;
64 }
65 }
66 }
67
68 return parent::makePageLink( $title, $parameters, $title ? $title->getText() : null );
69 }
70
71 protected function getMessageKey() {
72 $key = parent::getMessageKey();
73 $params = $this->getMessageParameters();
74 if ( !isset( $params[3] ) && !isset( $params[4] ) ) {
75 // Messages: logentry-rights-rights-legacy
76 $key .= '-legacy';
77 }
78
79 return $key;
80 }
81
82 protected function getMessageParameters() {
83 $params = parent::getMessageParameters();
84
85 // Really old entries that lack old/new groups,
86 // so don't try to process them
87 if ( !$this->shouldProcessParams( $params ) ) {
88 return $params;
89 }
90
91 // Groups are stored as [ name => expiry|null ]
92 $oldGroups = $this->getOldGroups( $params );
93 $newGroups = $this->getNewGroups( $params );
94
95 // These params were used in the past, when the log message said "from: X to: Y"
96 // They were kept not to break translations
97 if ( count( $oldGroups ) ) {
98 $params[3] = Message::rawParam( $this->formatRightsList( $oldGroups ) );
99 } else {
100 $params[3] = $this->msg( 'rightsnone' )->text();
101 }
102 if ( count( $newGroups ) ) {
103 $params[4] = Message::rawParam( $this->formatRightsList( $newGroups ) );
104 } else {
105 $params[4] = $this->msg( 'rightsnone' )->text();
106 }
107
108 $performerName = $params[1];
109 $userName = $this->entry->getTarget()->getText();
110
111 $params[5] = $userName;
112
113 $groupChanges = $this->classifyGroupChanges( $oldGroups, $newGroups );
114
115 // The following messages are used here:
116 // * logentry-rights-rights-granted
117 // * logentry-rights-rights-revoked
118 // * logentry-rights-rights-expiry-changed
119 // * logentry-rights-rights-kept
120 // * logentry-rights-autopromote-granted
121 // * logentry-rights-autopromote-revoked
122 // * logentry-rights-autopromote-expiry-changed
123 // * logentry-rights-autopromote-kept
124 $messagePrefix = 'logentry-rights-rights-';
125 if ( $this->entry->getSubtype() === 'autopromote' ) {
126 $messagePrefix = 'logentry-rights-autopromote-';
127 }
128
129 $params[6] = $this->formatChangesToGroups( $groupChanges, $performerName, $userName,
130 $messagePrefix );
131
132 return $params;
133 }
134
144 protected function shouldProcessParams( array $params ) {
145 return isset( $params[3] ) || isset( $params[4] );
146 }
147
157 protected function getOldGroups( array $params ) {
158 if ( !isset( $params[3] ) ) {
159 return [];
160 }
161
162 $allParams = $this->entry->getParameters();
163 return $this->joinGroupsWithExpiries( $params[3], $allParams['oldmetadata'] ?? [] );
164 }
165
175 protected function getNewGroups( array $params ) {
176 if ( !isset( $params[4] ) ) {
177 return [];
178 }
179
180 $allParams = $this->entry->getParameters();
181 return $this->joinGroupsWithExpiries( $params[4], $allParams['newmetadata'] ?? [] );
182 }
183
196 protected function joinGroupsWithExpiries( $groupNames, array $metadata ) {
197 $groupNames = $this->makeGroupArray( $groupNames );
198 if ( !$this->plaintext && count( $groupNames ) ) {
199 $this->replaceGroupsWithMemberNames( $groupNames );
200 }
201
202 $expiries = [];
203 foreach (
204 array_map( null, $groupNames, $metadata )
205 as [ $groupName, $groupMetadata ]
206 ) {
207 if ( isset( $groupMetadata['expiry'] ) ) {
208 $expiry = $groupMetadata['expiry'];
209 } else {
210 $expiry = null;
211 }
212 $expiries[$groupName] = $expiry;
213 }
214 return $expiries;
215 }
216
224 protected function replaceGroupsWithMemberNames( array &$groupNames ) {
225 $lang = $this->context->getLanguage();
226 $userName = $this->entry->getTarget()->getText();
227 foreach ( $groupNames as &$group ) {
228 $group = $lang->getGroupMemberName( $group, $userName );
229 }
230 }
231
247 protected function classifyGroupChanges( array $oldGroups, array $newGroups ) {
248 $granted = array_diff_key( $newGroups, $oldGroups );
249 $revoked = array_diff_key( $oldGroups, $newGroups );
250 $kept = array_intersect_key( $oldGroups, $newGroups );
251
252 $expiryChanged = [];
253 $noChange = [];
254
255 foreach ( $kept as $group => $oldExpiry ) {
256 $newExpiry = $newGroups[$group];
257 if ( $oldExpiry !== $newExpiry ) {
258 $expiryChanged[$group] = [ $oldExpiry, $newExpiry ];
259 } else {
260 $noChange[$group] = $oldExpiry;
261 }
262 }
263
264 // These contain both group names and their expiry times
265 // in case of 'expiry-changed', the times are in an array [ old, new ]
266 return [
267 'granted' => $granted,
268 'revoked' => $revoked,
269 'expiry-changed' => $expiryChanged,
270 'kept' => $noChange,
271 ];
272 }
273
284 protected function formatChangesToGroups( array $groupChanges, string $performerName,
285 string $targetName, string $messagePrefix = 'logentry-rights-rights-'
286 ) {
287 $formattedChanges = [];
288
289 foreach ( $groupChanges as $changeType => $groups ) {
290 if ( !count( $groups ) ) {
291 continue;
292 }
293
294 if ( $changeType === 'expiry-changed' ) {
295 $formattedList = $this->formatRightsListExpiryChanged( $groups );
296 } else {
297 $formattedList = $this->formatRightsList( $groups );
298 }
299
300 $formattedChanges[] = $this->msg(
301 $messagePrefix . $changeType,
302 $formattedList,
303 $performerName,
304 $targetName
305 );
306 }
307
308 $uiLanguage = $this->context->getLanguage();
309 return $uiLanguage->semicolonList( $formattedChanges );
310 }
311
312 private function formatRightsListExpiryChanged( array $groups ): string {
313 $list = [];
314
315 foreach ( $groups as $group => [ $oldExpiry, $newExpiry ] ) {
316 $oldExpiryFormatted = $oldExpiry ? $this->formatDate( $oldExpiry ) : false;
317 $newExpiryFormatted = $newExpiry ? $this->formatDate( $newExpiry ) : false;
318
319 if ( $oldExpiryFormatted && $newExpiryFormatted ) {
320 // The expiration was changed
321 $list[] = $this->msg( 'rightslogentry-expiry-changed' )->params(
322 $group,
323 $newExpiryFormatted['whole'],
324 $newExpiryFormatted['date'],
325 $newExpiryFormatted['time'],
326 $oldExpiryFormatted['whole'],
327 $oldExpiryFormatted['date'],
328 $oldExpiryFormatted['time']
329 )->parse();
330 } elseif ( $oldExpiryFormatted ) {
331 // The expiration was removed
332 $list[] = $this->msg( 'rightslogentry-expiry-removed' )->params(
333 $group,
334 $oldExpiryFormatted['whole'],
335 $oldExpiryFormatted['date'],
336 $oldExpiryFormatted['time']
337 )->parse();
338 } elseif ( $newExpiryFormatted ) {
339 // The expiration was added
340 $list[] = $this->msg( 'rightslogentry-expiry-set' )->params(
341 $group,
342 $newExpiryFormatted['whole'],
343 $newExpiryFormatted['date'],
344 $newExpiryFormatted['time']
345 )->parse();
346 } else {
347 // The rights are and were permanent
348 // Shouldn't happen as we process only changes to expiry time here
349 $list[] = htmlspecialchars( $group );
350 }
351 }
352
353 $uiLanguage = $this->context->getLanguage();
354 return $uiLanguage->listToText( $list );
355 }
356
357 private function formatRightsList( array $groups ): string {
358 $uiLanguage = $this->context->getLanguage();
359 // separate arrays of temporary and permanent memberships
360 $tempList = $permList = [];
361
362 foreach ( $groups as $group => $expiry ) {
363 if ( $expiry ) {
364 // format the group and expiry into a friendly string
365 $expiryFormatted = $this->formatDate( $expiry );
366 $tempList[] = $this->msg( 'rightslogentry-temporary-group' )->params( $group,
367 $expiryFormatted['whole'], $expiryFormatted['date'], $expiryFormatted['time'] )
368 ->parse();
369 } else {
370 // the right does not expire; just insert the group name
371 $permList[] = htmlspecialchars( $group );
372 }
373 }
374
375 // place all temporary memberships first, to avoid the ambiguity of
376 // "administrator, bureaucrat and importer (temporary, until X time)"
377 return $uiLanguage->listToText( array_merge( $tempList, $permList ) );
378 }
379
380 private function formatDate( string $date ): array {
381 $uiLanguage = $this->context->getLanguage();
382 $uiUser = $this->context->getUser();
383
384 return [
385 'whole' => $uiLanguage->userTimeAndDate( $date, $uiUser ),
386 'date' => $uiLanguage->userDate( $date, $uiUser ),
387 'time' => $uiLanguage->userTime( $date, $uiUser ),
388 ];
389 }
390
391 protected function getParametersForApi() {
392 $entry = $this->entry;
393 $params = $entry->getParameters();
394
395 static $map = [
396 '4:array:oldgroups',
397 '5:array:newgroups',
398 '4::oldgroups' => '4:array:oldgroups',
399 '5::newgroups' => '5:array:newgroups',
400 ];
401 foreach ( $map as $index => $key ) {
402 if ( isset( $params[$index] ) ) {
403 $params[$key] = $params[$index];
404 unset( $params[$index] );
405 }
406 }
407
408 // Really old entries do not have log params, so form them from whatever info
409 // we have.
410 // Also walk through the parallel arrays of groups and metadata, combining each
411 // metadata array with the name of the group it pertains to
412 if ( isset( $params['4:array:oldgroups'] ) ) {
413 $params['4:array:oldgroups'] = $this->makeGroupArray( $params['4:array:oldgroups'] );
414
415 $oldmetadata =& $params['oldmetadata'];
416 // unset old metadata entry to ensure metadata goes at the end of the params array
417 unset( $params['oldmetadata'] );
418 $params['oldmetadata'] = array_map( static function ( $index ) use ( $params, $oldmetadata ) {
419 $result = [ 'group' => $params['4:array:oldgroups'][$index] ];
420 if ( isset( $oldmetadata[$index] ) ) {
421 $result += $oldmetadata[$index];
422 }
423 $result['expiry'] = ApiResult::formatExpiry( $result['expiry'] ?? null );
424
425 return $result;
426 }, array_keys( $params['4:array:oldgroups'] ) );
427 }
428
429 if ( isset( $params['5:array:newgroups'] ) ) {
430 $params['5:array:newgroups'] = $this->makeGroupArray( $params['5:array:newgroups'] );
431
432 $newmetadata =& $params['newmetadata'];
433 // unset old metadata entry to ensure metadata goes at the end of the params array
434 unset( $params['newmetadata'] );
435 $params['newmetadata'] = array_map( static function ( $index ) use ( $params, $newmetadata ) {
436 $result = [ 'group' => $params['5:array:newgroups'][$index] ];
437 if ( isset( $newmetadata[$index] ) ) {
438 $result += $newmetadata[$index];
439 }
440 $result['expiry'] = ApiResult::formatExpiry( $result['expiry'] ?? null );
441
442 return $result;
443 }, array_keys( $params['5:array:newgroups'] ) );
444 }
445
446 return $params;
447 }
448
449 public function formatParametersForApi() {
450 $ret = parent::formatParametersForApi();
451 if ( isset( $ret['oldgroups'] ) ) {
452 ApiResult::setIndexedTagName( $ret['oldgroups'], 'g' );
453 }
454 if ( isset( $ret['newgroups'] ) ) {
455 ApiResult::setIndexedTagName( $ret['newgroups'], 'g' );
456 }
457 if ( isset( $ret['oldmetadata'] ) ) {
458 ApiResult::setArrayType( $ret['oldmetadata'], 'array' );
459 ApiResult::setIndexedTagName( $ret['oldmetadata'], 'g' );
460 }
461 if ( isset( $ret['newmetadata'] ) ) {
462 ApiResult::setArrayType( $ret['newmetadata'], 'array' );
463 ApiResult::setIndexedTagName( $ret['newmetadata'], 'g' );
464 }
465 return $ret;
466 }
467
471 private function makeGroupArray( $group ): array {
472 // Migrate old group params from string to array
473 if ( $group === '' ) {
474 $group = [];
475 } elseif ( is_string( $group ) ) {
476 $group = array_map( 'trim', explode( ',', $group ) );
477 }
478 return $group;
479 }
480}
481
483class_alias( RightsLogFormatter::class, 'RightsLogFormatter' );
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:82
This class represents the result of the API operations.
Definition ApiResult.php:45
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.
getParametersForApi()
Get the array of parameters, converted from legacy format if necessary.
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.
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.
makePageLink(?Title $title=null, $parameters=[], $html=null)
Helper to make a link to the page, taking the plaintext value in consideration.
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:157
Represents a title within MediaWiki.
Definition Title.php:78
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:31
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...