MediaWiki REL1_35
LogPager.php
Go to the documentation of this file.
1<?php
27
33 private $types = [];
34
36 private $performer = '';
37
39 private $title = '';
40
42 private $pattern = false;
43
45 private $typeCGI = '';
46
48 private $action = '';
49
52
55
57 private $mConds;
58
60 private $mTagFilter;
61
64
79 public function __construct( $list, $types = [], $performer = '', $title = '',
80 $pattern = false, $conds = [], $year = false, $month = false, $day = false,
81 $tagFilter = '', $action = '', $logId = 0
82 ) {
83 parent::__construct( $list->getContext() );
84 $this->mConds = $conds;
85
86 $this->mLogEventsList = $list;
87
88 $this->limitType( $types ); // also excludes hidden types
89 $this->limitLogId( $logId );
90 $this->limitFilterTypes();
91 $this->limitPerformer( $performer );
92 $this->limitTitle( $title, $pattern );
93 $this->limitAction( $action );
94 $this->getDateCond( $year, $month, $day );
95 $this->mTagFilter = (string)$tagFilter;
96
97 $this->mDb = wfGetDB( DB_REPLICA, 'logpager' );
98 }
99
100 public function getDefaultQuery() {
101 $query = parent::getDefaultQuery();
102 $query['type'] = $this->typeCGI; // arrays won't work here
103 $query['user'] = $this->performer;
104 $query['day'] = $this->mDay;
105 $query['month'] = $this->mMonth;
106 $query['year'] = $this->mYear;
107
108 return $query;
109 }
110
111 private function limitFilterTypes() {
112 if ( $this->hasEqualsClause( 'log_id' ) ) { // T220834
113 return;
114 }
115 $filterTypes = $this->getFilterParams();
116 foreach ( $filterTypes as $type => $hide ) {
117 if ( $hide ) {
118 $this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type );
119 }
120 }
121 }
122
123 public function getFilterParams() {
124 $filters = [];
125 if ( count( $this->types ) ) {
126 return $filters;
127 }
128
129 $wpfilters = $this->getRequest()->getArray( "wpfilters" );
130 $filterLogTypes = $this->getConfig()->get( 'FilterLogTypes' );
131
132 foreach ( $filterLogTypes as $type => $default ) {
133 // Back-compat: Check old URL params if the new param wasn't passed
134 if ( $wpfilters === null ) {
135 $hide = $this->getRequest()->getBool( "hide_{$type}_log", $default );
136 } else {
137 $hide = !in_array( $type, $wpfilters );
138 }
139
140 $filters[$type] = $hide;
141 }
142
143 return $filters;
144 }
145
153 private function limitType( $types ) {
154 $user = $this->getUser();
155 $restrictions = $this->getConfig()->get( 'LogRestrictions' );
156 // If $types is not an array, make it an array
157 $types = ( $types === '' ) ? [] : (array)$types;
158 // Don't even show header for private logs; don't recognize it...
159 $needReindex = false;
160 foreach ( $types as $type ) {
161 if ( isset( $restrictions[$type] )
162 && !MediaWikiServices::getInstance()
163 ->getPermissionManager()
164 ->userHasRight( $user, $restrictions[$type] )
165 ) {
166 $needReindex = true;
167 $types = array_diff( $types, [ $type ] );
168 }
169 }
170 if ( $needReindex ) {
171 // Lots of this code makes assumptions that
172 // the first entry in the array is $types[0].
173 $types = array_values( $types );
174 }
175 $this->types = $types;
176 // Don't show private logs to unprivileged users.
177 // Also, only show them upon specific request to avoid suprises.
178 $audience = $types ? 'user' : 'public';
179 $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience, $user );
180 if ( $hideLogs !== false ) {
181 $this->mConds[] = $hideLogs;
182 }
183 if ( count( $types ) ) {
184 $this->mConds['log_type'] = $types;
185 // Set typeCGI; used in url param for paging
186 if ( count( $types ) == 1 ) {
187 $this->typeCGI = $types[0];
188 }
189 }
190 }
191
198 private function limitPerformer( $name ) {
199 if ( $name == '' ) {
200 return;
201 }
202 $usertitle = Title::makeTitleSafe( NS_USER, $name );
203 if ( $usertitle === null ) {
204 return;
205 }
206 // Normalize username first so that non-existent users used
207 // in maintenance scripts work
208 $name = $usertitle->getText();
209
210 // Assume no joins required for log_user
211 $this->mConds[] = ActorMigration::newMigration()->getWhere(
212 wfGetDB( DB_REPLICA ), 'log_user', User::newFromName( $name, false )
213 )['conds'];
214
216
217 $this->performer = $name;
218 }
219
228 private function limitTitle( $page, $pattern ) {
229 if ( $page instanceof Title ) {
230 $title = $page;
231 } else {
232 $title = Title::newFromText( $page );
233 if ( strlen( $page ) == 0 || !$title instanceof Title ) {
234 return;
235 }
236 }
237
238 $this->title = $title->getPrefixedText();
239 $ns = $title->getNamespace();
240 $db = $this->mDb;
241
242 $interwikiDelimiter = $this->getConfig()->get( 'UserrightsInterwikiDelimiter' );
243
244 $doUserRightsLogLike = false;
245 if ( $this->types == [ 'rights' ] ) {
246 $parts = explode( $interwikiDelimiter, $title->getDBkey() );
247 if ( count( $parts ) == 2 ) {
248 list( $name, $database ) = array_map( 'trim', $parts );
249 if ( strstr( $database, '*' ) ) { // Search for wildcard in database name
250 $doUserRightsLogLike = true;
251 }
252 }
253 }
254
268 $this->mConds['log_namespace'] = $ns;
269 if ( $doUserRightsLogLike ) {
270 $params = [ $name . $interwikiDelimiter ];
271 foreach ( explode( '*', $database ) as $databasepart ) {
272 $params[] = $databasepart;
273 $params[] = $db->anyString();
274 }
275 array_pop( $params ); // Get rid of the last % we added.
276 $this->mConds[] = 'log_title' . $db->buildLike( ...$params );
277 } elseif ( $pattern && !$this->getConfig()->get( 'MiserMode' ) ) {
278 $this->mConds[] = 'log_title' . $db->buildLike( $title->getDBkey(), $db->anyString() );
279 $this->pattern = $pattern;
280 } else {
281 $this->mConds['log_title'] = $title->getDBkey();
282 }
284 }
285
291 private function limitAction( $action ) {
292 // Allow to filter the log by actions
294 if ( $type === '' ) {
295 // nothing to do
296 return;
297 }
298 $actions = $this->getConfig()->get( 'ActionFilteredLogs' );
299 if ( isset( $actions[$type] ) ) {
300 // log type can be filtered by actions
301 $this->mLogEventsList->setAllowedActions( array_keys( $actions[$type] ) );
302 if ( $action !== '' && isset( $actions[$type][$action] ) ) {
303 // add condition to query
304 $this->mConds['log_action'] = $actions[$type][$action];
305 $this->action = $action;
306 }
307 }
308 }
309
314 protected function limitLogId( $logId ) {
315 if ( !$logId ) {
316 return;
317 }
318 $this->mConds['log_id'] = $logId;
319 }
320
326 public function getQueryInfo() {
327 $basic = DatabaseLogEntry::getSelectQueryData();
328
329 $tables = $basic['tables'];
330 $fields = $basic['fields'];
331 $conds = $basic['conds'];
332 $options = $basic['options'];
333 $joins = $basic['join_conds'];
334
335 # Add log_search table if there are conditions on it.
336 # This filters the results to only include log rows that have
337 # log_search records with the specified ls_field and ls_value values.
338 if ( array_key_exists( 'ls_field', $this->mConds ) ) {
339 $tables[] = 'log_search';
340 $options['IGNORE INDEX'] = [ 'log_search' => 'ls_log_id' ];
341 $options['USE INDEX'] = [ 'logging' => 'PRIMARY' ];
342 if ( !$this->hasEqualsClause( 'ls_field' )
343 || !$this->hasEqualsClause( 'ls_value' )
344 ) {
345 # Since (ls_field,ls_value,ls_logid) is unique, if the condition is
346 # to match a specific (ls_field,ls_value) tuple, then there will be
347 # no duplicate log rows. Otherwise, we need to remove the duplicates.
348 $options[] = 'DISTINCT';
349 }
350 }
351 # Don't show duplicate rows when using log_search
352 $joins['log_search'] = [ 'JOIN', 'ls_log_id=log_id' ];
353
354 // T221458: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor` before
355 // `logging` and filesorting is somehow better than querying $limit+1 rows from `logging`.
356 // Tell it not to reorder the query. But not when tag filtering or log_search was used, as it
357 // seems as likely to be harmed as helped in that case.
358 if ( $this->mTagFilter === '' && !array_key_exists( 'ls_field', $this->mConds ) ) {
359 $options[] = 'STRAIGHT_JOIN';
360 }
361 if ( $this->performer !== '' || $this->types !== [] ) {
362 // T223151, T237026: MariaDB's optimizer, at least 10.1, likes to choose a wildly bad plan for
363 // some reason for these code paths. Tell it not to use the wrong index it wants to pick.
364 $options['IGNORE INDEX'] = [ 'logging' => [ 'times' ] ];
365 }
366
367 $options['MAX_EXECUTION_TIME'] = $this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' );
368
369 $info = [
370 'tables' => $tables,
371 'fields' => $fields,
372 'conds' => array_merge( $conds, $this->mConds ),
373 'options' => $options,
374 'join_conds' => $joins,
375 ];
376 # Add ChangeTags filter query
377 ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'],
378 $info['join_conds'], $info['options'], $this->mTagFilter );
379
380 return $info;
381 }
382
388 protected function hasEqualsClause( $field ) {
389 return (
390 array_key_exists( $field, $this->mConds ) &&
391 ( !is_array( $this->mConds[$field] ) || count( $this->mConds[$field] ) == 1 )
392 );
393 }
394
395 public function getIndexField() {
396 return 'log_timestamp';
397 }
398
399 protected function getStartBody() {
400 # Do a link batch query
401 if ( $this->getNumRows() > 0 ) {
402 $lb = new LinkBatch;
403 foreach ( $this->mResult as $row ) {
404 $lb->add( $row->log_namespace, $row->log_title );
405 $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
406 $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
407 $formatter = LogFormatter::newFromRow( $row );
408 foreach ( $formatter->getPreloadTitles() as $title ) {
409 $lb->addObj( $title );
410 }
411 }
412 $lb->execute();
413 $this->mResult->seek( 0 );
414 }
415
416 return '';
417 }
418
419 public function formatRow( $row ) {
420 return $this->mLogEventsList->logLine( $row );
421 }
422
423 public function getType() {
424 return $this->types;
425 }
426
432 public function getPerformer() {
433 return $this->performer;
434 }
435
439 public function getPage() {
440 return $this->title;
441 }
442
446 public function getPattern() {
447 return $this->pattern;
448 }
449
450 public function getYear() {
451 return $this->mYear;
452 }
453
454 public function getMonth() {
455 return $this->mMonth;
456 }
457
458 public function getDay() {
459 return $this->mDay;
460 }
461
462 public function getTagFilter() {
463 return $this->mTagFilter;
464 }
465
466 public function getAction() {
467 return $this->action;
468 }
469
470 public function doQuery() {
471 // Workaround MySQL optimizer bug
472 $this->mDb->setBigSelects();
473 parent::doQuery();
474 $this->mDb->setBigSelects( 'default' );
475 }
476
480 private function enforceActionRestrictions() {
481 if ( $this->actionRestrictionsEnforced ) {
482 return;
483 }
484 $this->actionRestrictionsEnforced = true;
485 $user = $this->getUser();
486 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
487 if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
488 $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) . ' = 0';
489 } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
490 $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_ACTION ) .
492 }
493 }
494
498 private function enforcePerformerRestrictions() {
499 // Same as enforceActionRestrictions(), except for _USER instead of _ACTION bits.
500 if ( $this->performerRestrictionsEnforced ) {
501 return;
502 }
503 $this->performerRestrictionsEnforced = true;
504 $user = $this->getUser();
505 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
506 if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
507 $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0';
508 } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
509 $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_USER ) .
511 }
512 }
513}
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
getUser()
Stable to override.
IDatabase $mDb
getNumRows()
Get the number of rows in the result set.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
add( $ns, $dbkey)
static newFromRow( $row)
Handy shortcut for constructing a formatter directly from database row.
const SUPPRESSED_USER
Definition LogPage.php:44
const DELETED_USER
Definition LogPage.php:40
const DELETED_ACTION
Definition LogPage.php:38
const SUPPRESSED_ACTION
Definition LogPage.php:45
hasEqualsClause( $field)
Checks if $this->mConds has $field matched to a single value.
Definition LogPager.php:388
bool $actionRestrictionsEnforced
Definition LogPager.php:54
bool $performerRestrictionsEnforced
Definition LogPager.php:51
string Title $title
Events limited to those about Title when set.
Definition LogPager.php:39
formatRow( $row)
Returns an HTML string representing the result row $row.
Definition LogPager.php:419
limitType( $types)
Set the log reader to return only entries of the given type.
Definition LogPager.php:153
getStartBody()
Hook into getBody(), allows text to be inserted at the start.
Definition LogPager.php:399
limitLogId( $logId)
Limit to the (single) specified log ID.
Definition LogPager.php:314
string $performer
Events limited to those by performer when set.
Definition LogPager.php:36
array $mConds
Definition LogPager.php:57
bool $pattern
Definition LogPager.php:42
getFilterParams()
Definition LogPager.php:123
limitFilterTypes()
Definition LogPager.php:111
__construct( $list, $types=[], $performer='', $title='', $pattern=false, $conds=[], $year=false, $month=false, $day=false, $tagFilter='', $action='', $logId=0)
Definition LogPager.php:79
enforcePerformerRestrictions()
Paranoia: avoid brute force searches (T19342)
Definition LogPager.php:498
enforceActionRestrictions()
Paranoia: avoid brute force searches (T19342)
Definition LogPager.php:480
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
Definition LogPager.php:100
limitTitle( $page, $pattern)
Set the log reader to return only entries affecting the given page.
Definition LogPager.php:228
string $mTagFilter
Definition LogPager.php:60
string $action
Definition LogPager.php:48
getTagFilter()
Definition LogPager.php:462
LogEventsList $mLogEventsList
Definition LogPager.php:63
getQueryInfo()
Constructs the most part of the query.
Definition LogPager.php:326
doQuery()
Do the query, using information from the object context.
Definition LogPager.php:470
getPerformer()
Guaranteed to either return a valid title string or a Zero-Length String.
Definition LogPager.php:432
string $typeCGI
Definition LogPager.php:45
limitPerformer( $name)
Set the log reader to return only entries by the given user.
Definition LogPager.php:198
array $types
Log types.
Definition LogPager.php:33
getIndexField()
Returns the name of the index field.
Definition LogPager.php:395
limitAction( $action)
Set the log_action field to a specified value (or values)
Definition LogPager.php:291
MediaWikiServices is the service locator for the application scope of MediaWiki.
Efficient paging for SQL queries.
getDateCond( $year, $month, $day=-1)
Set and return the mOffset timestamp such that we can get all revisions with a timestamp up to the sp...
Represents a title within MediaWiki.
Definition Title.php:42
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1041
getDBkey()
Get the main part with underscores.
Definition Title.php:1032
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1859
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
const NS_USER
Definition Defines.php:72
const NS_USER_TALK
Definition Defines.php:73
const DB_REPLICA
Definition defines.php:25