MediaWiki master
SpecialLog.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Specials;
22
42use Wikimedia\IPUtils;
44use Wikimedia\Timestamp\TimestampException;
45
51class SpecialLog extends SpecialPage {
52
53 private LinkBatchFactory $linkBatchFactory;
54
55 private IConnectionProvider $dbProvider;
56
57 private ActorNormalization $actorNormalization;
58
59 private UserIdentityLookup $userIdentityLookup;
60
61 private UserNameUtils $userNameUtils;
62
63 private LogFormatterFactory $logFormatterFactory;
64
65 public function __construct(
66 LinkBatchFactory $linkBatchFactory,
67 IConnectionProvider $dbProvider,
68 ActorNormalization $actorNormalization,
69 UserIdentityLookup $userIdentityLookup,
70 UserNameUtils $userNameUtils,
71 LogFormatterFactory $logFormatterFactory
72 ) {
73 parent::__construct( 'Log' );
74 $this->linkBatchFactory = $linkBatchFactory;
75 $this->dbProvider = $dbProvider;
76 $this->actorNormalization = $actorNormalization;
77 $this->userIdentityLookup = $userIdentityLookup;
78 $this->userNameUtils = $userNameUtils;
79 $this->logFormatterFactory = $logFormatterFactory;
80 }
81
82 public function execute( $par ) {
83 $this->setHeaders();
84 $this->outputHeader();
85 $out = $this->getOutput();
86 $out->addModuleStyles( 'mediawiki.interface.helpers.styles' );
87 $this->addHelpLink( 'Help:Log' );
88
89 $opts = new FormOptions;
90 $opts->add( 'type', '' );
91 $opts->add( 'user', '' );
92 $opts->add( 'page', '' );
93 $opts->add( 'pattern', false );
94 $opts->add( 'year', null, FormOptions::INTNULL );
95 $opts->add( 'month', null, FormOptions::INTNULL );
96 $opts->add( 'day', null, FormOptions::INTNULL );
97 $opts->add( 'tagfilter', '' );
98 $opts->add( 'tagInvert', false );
99 $opts->add( 'offset', '' );
100 $opts->add( 'dir', '' );
101 $opts->add( 'offender', '' );
102 $opts->add( 'subtype', '' );
103 $opts->add( 'logid', '' );
104
105 // Set values
106 if ( $par !== null ) {
107 $this->parseParams( (string)$par );
108 }
109 $opts->fetchValuesFromRequest( $this->getRequest() );
110
111 // Set date values
112 $dateString = $this->getRequest()->getVal( 'wpdate' );
113 if ( $dateString ) {
114 try {
115 $dateStamp = MWTimestamp::getInstance( $dateString . ' 00:00:00' );
116 } catch ( TimestampException $e ) {
117 // If users provide an invalid date, silently ignore it
118 // instead of letting an exception bubble up (T201411)
119 $dateStamp = false;
120 }
121 if ( $dateStamp ) {
122 $opts->setValue( 'year', (int)$dateStamp->format( 'Y' ) );
123 $opts->setValue( 'month', (int)$dateStamp->format( 'm' ) );
124 $opts->setValue( 'day', (int)$dateStamp->format( 'd' ) );
125 }
126 }
127
128 // If the user doesn't have the right permission to view the specific
129 // log type, throw a PermissionsError
130 $logRestrictions = $this->getConfig()->get( MainConfigNames::LogRestrictions );
131 $type = $opts->getValue( 'type' );
132 if ( isset( $logRestrictions[$type] )
133 && !$this->getAuthority()->isAllowed( $logRestrictions[$type] )
134 ) {
135 throw new PermissionsError( $logRestrictions[$type] );
136 }
137
138 # TODO: Move this into LogPager like other query conditions.
139 # Handle type-specific inputs
140 $qc = [];
141 $offenderName = $opts->getValue( 'offender' );
142 if ( $opts->getValue( 'type' ) == 'suppress' && $offenderName !== '' ) {
143 $dbr = $this->dbProvider->getReplicaDatabase();
144 $offenderId = $this->actorNormalization->findActorIdByName( $offenderName, $dbr );
145 if ( $offenderId ) {
146 $qc = [ 'ls_field' => 'target_author_actor', 'ls_value' => strval( $offenderId ) ];
147 } else {
148 // Unknown offender, thus results have to be empty
149 $qc = [ '1=0' ];
150 }
151 } else {
152 // Allow extensions to add relations to their search types
153 $this->getHookRunner()->onSpecialLogAddLogSearchRelations(
154 $opts->getValue( 'type' ), $this->getRequest(), $qc );
155 }
156
157 # TODO: Move this into LogEventList and use it as filter-callback in the field descriptor.
158 # Some log types are only for a 'User:' title but we might have been given
159 # only the username instead of the full title 'User:username'. This part try
160 # to lookup for a user by that name and eventually fix user input. See T3697.
161 if ( in_array( $opts->getValue( 'type' ), self::getLogTypesOnUser( $this->getHookRunner() ) ) ) {
162 # ok we have a type of log which expect a user title.
163 $page = $opts->getValue( 'page' );
164 $target = Title::newFromText( $page );
165 if ( $target && $target->getNamespace() === NS_MAIN ) {
166 if ( IPUtils::isValidRange( $target->getText() ) ) {
167 $page = IPUtils::sanitizeRange( $target->getText() );
168 }
169 # User forgot to add 'User:', we are adding it for them
170 $target = Title::makeTitleSafe( NS_USER, $page );
171 } elseif ( $target && $target->getNamespace() === NS_USER
172 && IPUtils::isValidRange( $target->getText() )
173 ) {
174 $ipOrRange = IPUtils::sanitizeRange( $target->getText() );
175 if ( $ipOrRange !== $target->getText() ) {
176 $target = Title::makeTitleSafe( NS_USER, $ipOrRange );
177 }
178 }
179 if ( $target !== null ) {
180 $page = $target->getPrefixedText();
181 $opts->setValue( 'page', $page );
182 $this->getRequest()->setVal( 'page', $page );
183 }
184 }
185
186 $this->show( $opts, $qc );
187 }
188
200 public static function getLogTypesOnUser( ?HookRunner $runner = null ) {
201 static $types = null;
202 if ( $types !== null ) {
203 return $types;
204 }
205 $types = [
206 'block',
207 'newusers',
208 'rights',
209 'renameuser',
210 ];
211
213 ->onGetLogTypesOnUser( $types );
214 return $types;
215 }
216
222 public function getSubpagesForPrefixSearch() {
223 $subpages = LogPage::validTypes();
224 $subpages[] = 'all';
225 sort( $subpages );
226 return $subpages;
227 }
228
237 private function parseParams( string $par ) {
238 # Get parameters
239 $parms = explode( '/', $par, 2 );
240 $symsForAll = [ '*', 'all' ];
241 if ( $parms[0] !== '' &&
242 ( in_array( $parms[0], LogPage::validTypes() ) || in_array( $parms[0], $symsForAll ) )
243 ) {
244 $this->getRequest()->setVal( 'type', $parms[0] );
245 if ( count( $parms ) === 2 ) {
246 $this->getRequest()->setVal( 'user', $parms[1] );
247 }
248 } elseif ( $par !== '' ) {
249 $this->getRequest()->setVal( 'user', $par );
250 }
251 }
252
253 private function show( FormOptions $opts, array $extraConds ) {
254 # Create a LogPager item to get the results and a LogEventsList item to format them...
255 $loglist = new LogEventsList(
256 $this->getContext(),
257 $this->getLinkRenderer(),
258 LogEventsList::USE_CHECKBOXES
259 );
260 $pager = new LogPager(
261 $loglist,
262 $opts->getValue( 'type' ),
263 $opts->getValue( 'user' ),
264 $opts->getValue( 'page' ),
265 $opts->getValue( 'pattern' ),
266 $extraConds,
267 $opts->getValue( 'year' ),
268 $opts->getValue( 'month' ),
269 $opts->getValue( 'day' ),
270 $opts->getValue( 'tagfilter' ),
271 $opts->getValue( 'subtype' ),
272 $opts->getValue( 'logid' ),
273 $this->linkBatchFactory,
274 $this->actorNormalization,
275 $this->logFormatterFactory,
276 $opts->getValue( 'tagInvert' )
277 );
278
279 # Set relevant user
280 $performer = $pager->getPerformer();
281 if ( $performer ) {
282 $performerUser = $this->userIdentityLookup->getUserIdentityByName( $performer );
283 // Only set valid local user as the relevant user (T344886)
284 // Uses the same condition as the SpecialContributions class did
285 if ( $performerUser && !IPUtils::isValidRange( $performer ) &&
286 ( $this->userNameUtils->isIP( $performer ) || $performerUser->isRegistered() )
287 ) {
288 $this->getSkin()->setRelevantUser( $performerUser );
289 }
290 }
291
292 # Show form options
293 $succeed = $loglist->showOptions(
294 $opts->getValue( 'type' ),
295 $opts->getValue( 'year' ),
296 $opts->getValue( 'month' ),
297 $opts->getValue( 'day' )
298 );
299 if ( !$succeed ) {
300 return;
301 }
302
303 $this->getOutput()->setPageTitleMsg(
304 ( new LogPage( $opts->getValue( 'type' ) ) )->getName()
305 );
306
307 # Insert list
308 $logBody = $pager->getBody();
309 if ( $logBody ) {
310 $this->getOutput()->addHTML(
311 $pager->getNavigationBar() .
312 $this->getActionButtons(
313 $loglist->beginLogEventsList() .
314 $logBody .
315 $loglist->endLogEventsList()
316 ) .
317 $pager->getNavigationBar()
318 );
319 } else {
320 $this->getOutput()->addWikiMsg( 'logempty' );
321 }
322 }
323
324 private function getActionButtons( string $formcontents ): string {
325 $canRevDelete = $this->getAuthority()
326 ->isAllowedAll( 'deletedhistory', 'deletelogentry' );
327 $showTagEditUI = ChangeTags::showTagEditingUI( $this->getAuthority() );
328 # If the user doesn't have the ability to delete log entries nor edit tags,
329 # don't bother showing them the button(s).
330 if ( !$canRevDelete && !$showTagEditUI ) {
331 return $formcontents;
332 }
333
334 # Show button to hide log entries and/or edit change tags
335 $s = Html::openElement(
336 'form',
337 [ 'action' => wfScript(), 'id' => 'mw-log-deleterevision-submit' ]
338 ) . "\n";
339 $s .= Html::hidden( 'type', 'logging' ) . "\n";
340
341 $buttons = '';
342 if ( $canRevDelete ) {
343 $buttons .= Html::element(
344 'button',
345 [
346 'type' => 'submit',
347 'name' => 'title',
348 'value' => SpecialPage::getTitleFor( 'Revisiondelete' )->getPrefixedDBkey(),
349 'class' => "deleterevision-log-submit mw-log-deleterevision-button mw-ui-button"
350 ],
351 $this->msg( 'showhideselectedlogentries' )->text()
352 ) . "\n";
353 }
354 if ( $showTagEditUI ) {
355 $buttons .= Html::element(
356 'button',
357 [
358 'type' => 'submit',
359 'name' => 'title',
360 'value' => SpecialPage::getTitleFor( 'EditTags' )->getPrefixedDBkey(),
361 'class' => "editchangetags-log-submit mw-log-editchangetags-button mw-ui-button"
362 ],
363 $this->msg( 'log-edit-tags' )->text()
364 ) . "\n";
365 }
366
367 $buttons .= ( new ListToggle( $this->getOutput() ) )->getHTML();
368
369 $s .= $buttons . $formcontents . $buttons;
370 $s .= Html::closeElement( 'form' );
371
372 return $s;
373 }
374
375 protected function getGroupName() {
376 return 'changes';
377 }
378}
379
381class_alias( SpecialLog::class, 'SpecialLog' );
const NS_USER
Definition Defines.php:67
const NS_MAIN
Definition Defines.php:65
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
Recent changes tagging.
Show an error when a user tries to do something they do not have the necessary permissions for.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Helper class to keep track of options when mixing links and form elements.
add( $name, $default, $type=self::AUTO)
Add an option to be handled by this FormOptions instance.
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
Class for generating clickable toggle links for a list of checkboxes.
Class to simplify the use of log pages.
Definition LogPage.php:50
A class containing constants representing the names of configuration variables.
const LogRestrictions
Name constant for the LogRestrictions setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Parent class for all special pages.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getSkin()
Shortcut to get the skin being used for this instance.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getAuthority()
Shortcut to get the Authority executing this instance.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages By default the message key is the canonical name of...
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
A special page that lists log entries.
static getLogTypesOnUser(?HookRunner $runner=null)
List log type for which the target is a user Thus if the given target is in NS_MAIN we can alter it t...
__construct(LinkBatchFactory $linkBatchFactory, IConnectionProvider $dbProvider, ActorNormalization $actorNormalization, UserIdentityLookup $userIdentityLookup, UserNameUtils $userNameUtils, LogFormatterFactory $logFormatterFactory)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
execute( $par)
Default execute method Checks user permissions.
getSubpagesForPrefixSearch()
Return an array of subpages that this special page will accept.
Represents a title within MediaWiki.
Definition Title.php:78
UserNameUtils service.
Library for creating and parsing MW-style timestamps.
$runner
Service for dealing with the actor table.
Service for looking up UserIdentity.
Provide primary and replica IDatabase connections.
element(SerializerNode $parent, SerializerNode $node, $contents)