MediaWiki  master
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 
54  private $actionRestrictionsEnforced = false;
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 = $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  }
283  $this->enforceActionRestrictions();
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() {
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  $info = [
368  'tables' => $tables,
369  'fields' => $fields,
370  'conds' => array_merge( $conds, $this->mConds ),
371  'options' => $options,
372  'join_conds' => $joins,
373  ];
374  # Add ChangeTags filter query
375  ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'],
376  $info['join_conds'], $info['options'], $this->mTagFilter );
377 
378  return $info;
379  }
380 
386  protected function hasEqualsClause( $field ) {
387  return (
388  array_key_exists( $field, $this->mConds ) &&
389  ( !is_array( $this->mConds[$field] ) || count( $this->mConds[$field] ) == 1 )
390  );
391  }
392 
393  function getIndexField() {
394  return 'log_timestamp';
395  }
396 
397  protected function getStartBody() {
398  # Do a link batch query
399  if ( $this->getNumRows() > 0 ) {
400  $lb = new LinkBatch;
401  foreach ( $this->mResult as $row ) {
402  $lb->add( $row->log_namespace, $row->log_title );
403  $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
404  $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
405  $formatter = LogFormatter::newFromRow( $row );
406  foreach ( $formatter->getPreloadTitles() as $title ) {
407  $lb->addObj( $title );
408  }
409  }
410  $lb->execute();
411  $this->mResult->seek( 0 );
412  }
413 
414  return '';
415  }
416 
417  public function formatRow( $row ) {
418  return $this->mLogEventsList->logLine( $row );
419  }
420 
421  public function getType() {
422  return $this->types;
423  }
424 
430  public function getPerformer() {
431  return $this->performer;
432  }
433 
437  public function getPage() {
438  return $this->title;
439  }
440 
444  public function getPattern() {
445  return $this->pattern;
446  }
447 
448  public function getYear() {
449  return $this->mYear;
450  }
451 
452  public function getMonth() {
453  return $this->mMonth;
454  }
455 
456  public function getDay() {
457  return $this->mDay;
458  }
459 
460  public function getTagFilter() {
461  return $this->mTagFilter;
462  }
463 
464  public function getAction() {
465  return $this->action;
466  }
467 
468  public function doQuery() {
469  // Workaround MySQL optimizer bug
470  $this->mDb->setBigSelects();
471  parent::doQuery();
472  $this->mDb->setBigSelects( 'default' );
473  }
474 
478  private function enforceActionRestrictions() {
479  if ( $this->actionRestrictionsEnforced ) {
480  return;
481  }
482  $this->actionRestrictionsEnforced = true;
483  $user = $this->getUser();
484  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
485  if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
486  $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) . ' = 0';
487  } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
488  $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_ACTION ) .
489  ' != ' . LogPage::SUPPRESSED_USER;
490  }
491  }
492 
496  private function enforcePerformerRestrictions() {
497  // Same as enforceActionRestrictions(), except for _USER instead of _ACTION bits.
498  if ( $this->performerRestrictionsEnforced ) {
499  return;
500  }
501  $this->performerRestrictionsEnforced = true;
502  $user = $this->getUser();
503  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
504  if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
505  $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0';
506  } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
507  $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_USER ) .
509  }
510  }
511 }
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:63
ReverseChronologicalPager\getDateCond
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...
Definition: ReverseChronologicalPager.php:77
LogPage\SUPPRESSED_ACTION
const SUPPRESSED_ACTION
Definition: LogPage.php:41
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:317
LogPage\SUPPRESSED_USER
const SUPPRESSED_USER
Definition: LogPage.php:40
LogPager\getPattern
getPattern()
Definition: LogPager.php:444
LogPager\getType
getType()
Definition: LogPager.php:421
LinkBatch
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:35
LinkBatch\add
add( $ns, $dbkey)
Definition: LinkBatch.php:130
LogPager\getPerformer
getPerformer()
Guaranteed to either return a valid title string or a Zero-Length String.
Definition: LogPager.php:430
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
LogPager\limitTitle
limitTitle( $page, $pattern)
Set the log reader to return only entries affecting the given page.
Definition: LogPager.php:228
ReverseChronologicalPager\$mMonth
int $mMonth
Definition: ReverseChronologicalPager.php:34
LogPager\getMonth
getMonth()
Definition: LogPager.php:452
LogPager\getFilterParams
getFilterParams()
Definition: LogPager.php:123
LogPager\$types
array $types
Log types.
Definition: LogPager.php:33
DatabaseLogEntry\getSelectQueryData
static getSelectQueryData()
Returns array of information that is needed for querying log entries.
Definition: DatabaseLogEntry.php:43
LogPager\getQueryInfo
getQueryInfo()
Constructs the most part of the query.
Definition: LogPager.php:326
LogPager\$performerRestrictionsEnforced
bool $performerRestrictionsEnforced
Definition: LogPager.php:51
Title\getPrefixedText
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1853
LogPager
Definition: LogPager.php:31
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:537
LogPager\$mConds
array $mConds
Definition: LogPager.php:57
ContextSource\getRequest
getRequest()
Definition: ContextSource.php:71
ContextSource\getUser
getUser()
Definition: ContextSource.php:120
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:139
LogPager\getPage
getPage()
Definition: LogPager.php:437
LogPager\__construct
__construct( $list, $types=[], $performer='', $title='', $pattern=false, $conds=[], $year=false, $month=false, $day=false, $tagFilter='', $action='', $logId=0)
Definition: LogPager.php:79
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:776
LogPager\$title
string Title $title
Events limited to those about Title when set.
Definition: LogPager.php:39
Title\getDBkey
getDBkey()
Get the main part with underscores.
Definition: Title.php:1014
LogPager\$action
string $action
Definition: LogPager.php:48
LogPager\formatRow
formatRow( $row)
Abstract formatting function.
Definition: LogPager.php:417
LogFormatter\newFromRow
static newFromRow( $row)
Handy shortcut for constructing a formatter directly from database row.
Definition: LogFormatter.php:70
LogPager\getIndexField
getIndexField()
This function should be overridden to return the name of the index fi- eld.
Definition: LogPager.php:393
Title\getNamespace
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1035
LogPager\limitFilterTypes
limitFilterTypes()
Definition: LogPager.php:111
LogPage\DELETED_USER
const DELETED_USER
Definition: LogPage.php:36
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2562
IndexPager\$mDb
IDatabase $mDb
Definition: IndexPager.php:96
LogPager\$pattern
bool $pattern
Definition: LogPager.php:42
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
LogPager\$actionRestrictionsEnforced
bool $actionRestrictionsEnforced
Definition: LogPager.php:54
LogEventsList
Definition: LogEventsList.php:30
LogPage\DELETED_ACTION
const DELETED_ACTION
Definition: LogPage.php:34
LogPager\getStartBody
getStartBody()
Hook into getBody(), allows text to be inserted at the start.
Definition: LogPager.php:397
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:610
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:63
ReverseChronologicalPager\$mYear
int $mYear
Definition: ReverseChronologicalPager.php:32
LogEventsList\getExcludeClause
static getExcludeClause( $db, $audience='public', User $user=null)
SQL clause to skip forbidden log types for this user.
Definition: LogEventsList.php:782
LogPager\$mLogEventsList
LogEventsList $mLogEventsList
Definition: LogPager.php:63
LogPager\$typeCGI
string $typeCGI
Definition: LogPager.php:45
LogPager\doQuery
doQuery()
Do the query, using information from the object context.
Definition: LogPager.php:468
LogPager\getAction
getAction()
Definition: LogPager.php:464
LogPager\getTagFilter
getTagFilter()
Definition: LogPager.php:460
LogPager\$mTagFilter
string $mTagFilter
Definition: LogPager.php:60
LogPager\$performer
string $performer
Events limited to those by performer when set.
Definition: LogPager.php:36
LogPager\limitType
limitType( $types)
Set the log reader to return only entries of the given type.
Definition: LogPager.php:153
LogPager\limitPerformer
limitPerformer( $name)
Set the log reader to return only entries by the given user.
Definition: LogPager.php:198
Title
Represents a title within MediaWiki.
Definition: Title.php:42
LogPager\hasEqualsClause
hasEqualsClause( $field)
Checks if $this->mConds has $field matched to a single value.
Definition: LogPager.php:386
LogPager\enforceActionRestrictions
enforceActionRestrictions()
Paranoia: avoid brute force searches (T19342)
Definition: LogPager.php:478
LogPager\getDefaultQuery
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
Definition: LogPager.php:100
ReverseChronologicalPager\$mDay
int $mDay
Definition: ReverseChronologicalPager.php:36
LogPager\enforcePerformerRestrictions
enforcePerformerRestrictions()
Paranoia: avoid brute force searches (T19342)
Definition: LogPager.php:496
NS_USER
const NS_USER
Definition: Defines.php:62
LogPager\getDay
getDay()
Definition: LogPager.php:456
ReverseChronologicalPager
Efficient paging for SQL queries.
Definition: ReverseChronologicalPager.php:28
LogPager\limitAction
limitAction( $action)
Set the log_action field to a specified value (or values)
Definition: LogPager.php:291
IndexPager\getNumRows
getNumRows()
Get the number of rows in the result set.
Definition: IndexPager.php:609
LogPager\limitLogId
limitLogId( $logId)
Limit to the (single) specified log ID.
Definition: LogPager.php:314
LogPager\getYear
getYear()
Definition: LogPager.php:448
$type
$type
Definition: testCompression.php:50