MediaWiki  master
LogPager.php
Go to the documentation of this file.
1 <?php
28 
34  private $types = [];
35 
37  private $performer = '';
38 
40  private $title = '';
41 
43  private $pattern = false;
44 
46  private $typeCGI = '';
47 
49  private $action = '';
50 
53 
55  private $actionRestrictionsEnforced = false;
56 
58  private $mConds;
59 
61  private $mTagFilter;
62 
65 
68 
84  public function __construct( $list, $types = [], $performer = '', $title = '',
85  $pattern = false, $conds = [], $year = false, $month = false, $day = false,
86  $tagFilter = '', $action = '', $logId = 0,
88  ) {
89  parent::__construct( $list->getContext() );
90  $this->mConds = $conds;
91 
92  $this->mLogEventsList = $list;
93 
94  $this->limitType( $types ); // also excludes hidden types
95  $this->limitLogId( $logId );
96  $this->limitFilterTypes();
97  $this->limitPerformer( $performer );
98  $this->limitTitle( $title, $pattern );
99  $this->limitAction( $action );
100  $this->getDateCond( $year, $month, $day );
101  $this->mTagFilter = $tagFilter;
102  $this->linkBatchFactory = $linkBatchFactory ?? MediaWikiServices::getInstance()->getLinkBatchFactory();
103 
104  $this->mDb = wfGetDB( DB_REPLICA, 'logpager' );
105  }
106 
107  public function getDefaultQuery() {
108  $query = parent::getDefaultQuery();
109  $query['type'] = $this->typeCGI; // arrays won't work here
110  $query['user'] = $this->performer;
111  $query['day'] = $this->mDay;
112  $query['month'] = $this->mMonth;
113  $query['year'] = $this->mYear;
114 
115  return $query;
116  }
117 
118  private function limitFilterTypes() {
119  if ( $this->hasEqualsClause( 'log_id' ) ) { // T220834
120  return;
121  }
122  $filterTypes = $this->getFilterParams();
123  foreach ( $filterTypes as $type => $hide ) {
124  if ( $hide ) {
125  $this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type );
126  }
127  }
128  }
129 
130  public function getFilterParams() {
131  $filters = [];
132  if ( count( $this->types ) ) {
133  return $filters;
134  }
135 
136  $wpfilters = $this->getRequest()->getArray( "wpfilters" );
137  $filterLogTypes = $this->getConfig()->get( 'FilterLogTypes' );
138 
139  foreach ( $filterLogTypes as $type => $default ) {
140  // Back-compat: Check old URL params if the new param wasn't passed
141  if ( $wpfilters === null ) {
142  $hide = $this->getRequest()->getBool( "hide_{$type}_log", $default );
143  } else {
144  $hide = !in_array( $type, $wpfilters );
145  }
146 
147  $filters[$type] = $hide;
148  }
149 
150  return $filters;
151  }
152 
160  private function limitType( $types ) {
161  $user = $this->getUser();
162  $restrictions = $this->getConfig()->get( 'LogRestrictions' );
163  // If $types is not an array, make it an array
164  $types = ( $types === '' ) ? [] : (array)$types;
165  // Don't even show header for private logs; don't recognize it...
166  $needReindex = false;
167  foreach ( $types as $type ) {
168  if ( isset( $restrictions[$type] )
169  && !MediaWikiServices::getInstance()
170  ->getPermissionManager()
171  ->userHasRight( $user, $restrictions[$type] )
172  ) {
173  $needReindex = true;
174  $types = array_diff( $types, [ $type ] );
175  }
176  }
177  if ( $needReindex ) {
178  // Lots of this code makes assumptions that
179  // the first entry in the array is $types[0].
180  $types = array_values( $types );
181  }
182  $this->types = $types;
183  // Don't show private logs to unprivileged users.
184  // Also, only show them upon specific request to avoid suprises.
185  $audience = $types ? 'user' : 'public';
186  $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience, $user );
187  if ( $hideLogs !== false ) {
188  $this->mConds[] = $hideLogs;
189  }
190  if ( count( $types ) ) {
191  $this->mConds['log_type'] = $types;
192  // Set typeCGI; used in url param for paging
193  if ( count( $types ) == 1 ) {
194  $this->typeCGI = $types[0];
195  }
196  }
197  }
198 
205  private function limitPerformer( $name ) {
206  if ( $name == '' ) {
207  return;
208  }
209  $usertitle = Title::makeTitleSafe( NS_USER, $name );
210  if ( $usertitle === null ) {
211  return;
212  }
213  // Normalize username first so that non-existent users used
214  // in maintenance scripts work
215  $name = $usertitle->getText();
216 
217  // Assume no joins required for log_user
218  $this->mConds[] = ActorMigration::newMigration()->getWhere(
219  wfGetDB( DB_REPLICA ), 'log_user', User::newFromName( $name, false )
220  )['conds'];
221 
223 
224  $this->performer = $name;
225  }
226 
235  private function limitTitle( $page, $pattern ) {
236  if ( $page instanceof Title ) {
237  $title = $page;
238  } else {
239  $title = Title::newFromText( $page );
240  if ( strlen( $page ) == 0 || !$title instanceof Title ) {
241  return;
242  }
243  }
244 
245  $this->title = $title->getPrefixedText();
246  $ns = $title->getNamespace();
247  $db = $this->mDb;
248 
249  $interwikiDelimiter = $this->getConfig()->get( 'UserrightsInterwikiDelimiter' );
250 
251  $doUserRightsLogLike = false;
252  if ( $this->types == [ 'rights' ] ) {
253  $parts = explode( $interwikiDelimiter, $title->getDBkey() );
254  if ( count( $parts ) == 2 ) {
255  list( $name, $database ) = array_map( 'trim', $parts );
256  if ( strstr( $database, '*' ) ) { // Search for wildcard in database name
257  $doUserRightsLogLike = true;
258  }
259  }
260  }
261 
275  $this->mConds['log_namespace'] = $ns;
276  if ( $doUserRightsLogLike ) {
277  $params = [ $name . $interwikiDelimiter ];
278  foreach ( explode( '*', $database ) as $databasepart ) {
279  $params[] = $databasepart;
280  $params[] = $db->anyString();
281  }
282  array_pop( $params ); // Get rid of the last % we added.
283  $this->mConds[] = 'log_title' . $db->buildLike( ...$params );
284  } elseif ( $pattern && !$this->getConfig()->get( 'MiserMode' ) ) {
285  $this->mConds[] = 'log_title' . $db->buildLike( $title->getDBkey(), $db->anyString() );
286  $this->pattern = $pattern;
287  } else {
288  $this->mConds['log_title'] = $title->getDBkey();
289  }
290  $this->enforceActionRestrictions();
291  }
292 
298  private function limitAction( $action ) {
299  // Allow to filter the log by actions
301  if ( $type === '' ) {
302  // nothing to do
303  return;
304  }
305  $actions = $this->getConfig()->get( 'ActionFilteredLogs' );
306  if ( isset( $actions[$type] ) ) {
307  // log type can be filtered by actions
308  $this->mLogEventsList->setAllowedActions( array_keys( $actions[$type] ) );
309  if ( $action !== '' && isset( $actions[$type][$action] ) ) {
310  // add condition to query
311  $this->mConds['log_action'] = $actions[$type][$action];
312  $this->action = $action;
313  }
314  }
315  }
316 
321  protected function limitLogId( $logId ) {
322  if ( !$logId ) {
323  return;
324  }
325  $this->mConds['log_id'] = $logId;
326  }
327 
333  public function getQueryInfo() {
335 
336  $tables = $basic['tables'];
337  $fields = $basic['fields'];
338  $conds = $basic['conds'];
339  $options = $basic['options'];
340  $joins = $basic['join_conds'];
341 
342  # Add log_search table if there are conditions on it.
343  # This filters the results to only include log rows that have
344  # log_search records with the specified ls_field and ls_value values.
345  if ( array_key_exists( 'ls_field', $this->mConds ) ) {
346  $tables[] = 'log_search';
347  $options['IGNORE INDEX'] = [ 'log_search' => 'ls_log_id' ];
348  $options['USE INDEX'] = [ 'logging' => 'PRIMARY' ];
349  if ( !$this->hasEqualsClause( 'ls_field' )
350  || !$this->hasEqualsClause( 'ls_value' )
351  ) {
352  # Since (ls_field,ls_value,ls_logid) is unique, if the condition is
353  # to match a specific (ls_field,ls_value) tuple, then there will be
354  # no duplicate log rows. Otherwise, we need to remove the duplicates.
355  $options[] = 'DISTINCT';
356  }
357  }
358  # Don't show duplicate rows when using log_search
359  $joins['log_search'] = [ 'JOIN', 'ls_log_id=log_id' ];
360 
361  // T221458: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor` before
362  // `logging` and filesorting is somehow better than querying $limit+1 rows from `logging`.
363  // Tell it not to reorder the query. But not when tag filtering or log_search was used, as it
364  // seems as likely to be harmed as helped in that case.
365  if ( !$this->mTagFilter && !array_key_exists( 'ls_field', $this->mConds ) ) {
366  $options[] = 'STRAIGHT_JOIN';
367  }
368  if ( $this->performer !== '' || $this->types !== [] ) {
369  // T223151, T237026: MariaDB's optimizer, at least 10.1, likes to choose a wildly bad plan for
370  // some reason for these code paths. Tell it not to use the wrong index it wants to pick.
371  $options['IGNORE INDEX'] = [ 'logging' => [ 'times' ] ];
372  }
373 
374  $info = [
375  'tables' => $tables,
376  'fields' => $fields,
377  'conds' => array_merge( $conds, $this->mConds ),
378  'options' => $options,
379  'join_conds' => $joins,
380  ];
381  # Add ChangeTags filter query
382  ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'],
383  $info['join_conds'], $info['options'], $this->mTagFilter );
384 
385  return $info;
386  }
387 
393  protected function hasEqualsClause( $field ) {
394  return (
395  array_key_exists( $field, $this->mConds ) &&
396  ( !is_array( $this->mConds[$field] ) || count( $this->mConds[$field] ) == 1 )
397  );
398  }
399 
400  public function getIndexField() {
401  return 'log_timestamp';
402  }
403 
404  protected function getStartBody() {
405  # Do a link batch query
406  if ( $this->getNumRows() > 0 ) {
407  $lb = $this->linkBatchFactory->newLinkBatch();
408  foreach ( $this->mResult as $row ) {
409  $lb->add( $row->log_namespace, $row->log_title );
410  $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
411  $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
412  $formatter = LogFormatter::newFromRow( $row );
413  foreach ( $formatter->getPreloadTitles() as $title ) {
414  $lb->addObj( $title );
415  }
416  }
417  $lb->execute();
418  $this->mResult->seek( 0 );
419  }
420 
421  return '';
422  }
423 
424  public function formatRow( $row ) {
425  return $this->mLogEventsList->logLine( $row );
426  }
427 
428  public function getType() {
429  return $this->types;
430  }
431 
437  public function getPerformer() {
438  return $this->performer;
439  }
440 
444  public function getPage() {
445  return $this->title;
446  }
447 
451  public function getPattern() {
452  return $this->pattern;
453  }
454 
455  public function getYear() {
456  return $this->mYear;
457  }
458 
459  public function getMonth() {
460  return $this->mMonth;
461  }
462 
463  public function getDay() {
464  return $this->mDay;
465  }
466 
467  public function getTagFilter() {
468  return $this->mTagFilter;
469  }
470 
471  public function getAction() {
472  return $this->action;
473  }
474 
475  public function doQuery() {
476  // Workaround MySQL optimizer bug
477  $this->mDb->setBigSelects();
478  parent::doQuery();
479  $this->mDb->setBigSelects( 'default' );
480  }
481 
485  private function enforceActionRestrictions() {
486  if ( $this->actionRestrictionsEnforced ) {
487  return;
488  }
489  $this->actionRestrictionsEnforced = true;
490  $user = $this->getUser();
491  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
492  if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
493  $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) . ' = 0';
494  } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
495  $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_ACTION ) .
496  ' != ' . LogPage::SUPPRESSED_USER;
497  }
498  }
499 
503  private function enforcePerformerRestrictions() {
504  // Same as enforceActionRestrictions(), except for _USER instead of _ACTION bits.
505  if ( $this->performerRestrictionsEnforced ) {
506  return;
507  }
508  $this->performerRestrictionsEnforced = true;
509  $user = $this->getUser();
510  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
511  if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
512  $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0';
513  } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
514  $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_USER ) .
516  }
517  }
518 }
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:67
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:84
LogPage\SUPPRESSED_ACTION
const SUPPRESSED_ACTION
Definition: LogPage.php:45
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:328
LogPage\SUPPRESSED_USER
const SUPPRESSED_USER
Definition: LogPage.php:44
LogPager\getPattern
getPattern()
Definition: LogPager.php:451
LogPager\getType
getType()
Definition: LogPager.php:428
LogPager\getPerformer
getPerformer()
Guaranteed to either return a valid title string or a Zero-Length String.
Definition: LogPager.php:437
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:157
LogPager\limitTitle
limitTitle( $page, $pattern)
Set the log reader to return only entries affecting the given page.
Definition: LogPager.php:235
ReverseChronologicalPager\$mMonth
int $mMonth
Definition: ReverseChronologicalPager.php:35
LogPager\getMonth
getMonth()
Definition: LogPager.php:459
LogPager\getFilterParams
getFilterParams()
Definition: LogPager.php:130
LogPager\$types
array $types
Log types.
Definition: LogPager.php:34
DatabaseLogEntry\getSelectQueryData
static getSelectQueryData()
Returns array of information that is needed for querying log entries.
Definition: DatabaseLogEntry.php:45
LogPager\getQueryInfo
getQueryInfo()
Constructs the most part of the query.
Definition: LogPager.php:333
LogPager\$performerRestrictionsEnforced
bool $performerRestrictionsEnforced
Definition: LogPager.php:52
LogPager\__construct
__construct( $list, $types=[], $performer='', $title='', $pattern=false, $conds=[], $year=false, $month=false, $day=false, $tagFilter='', $action='', $logId=0, LinkBatchFactory $linkBatchFactory=null)
Definition: LogPager.php:84
Title\getPrefixedText
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1852
LogPager
Definition: LogPager.php:32
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:538
LogPager\$mConds
array $mConds
Definition: LogPager.php:58
ContextSource\getRequest
getRequest()
Definition: ContextSource.php:76
ContextSource\getUser
getUser()
Stable to override.
Definition: ContextSource.php:131
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:140
LogPager\$linkBatchFactory
LinkBatchFactory $linkBatchFactory
Definition: LogPager.php:67
LogPager\getPage
getPage()
Definition: LogPager.php:444
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:798
LogPager\$title
string Title $title
Events limited to those about Title when set.
Definition: LogPager.php:40
Title\getDBkey
getDBkey()
Get the main part with underscores.
Definition: Title.php:1025
LogPager\$action
string $action
Definition: LogPager.php:49
LogPager\formatRow
formatRow( $row)
Returns an HTML string representing the result row $row.
Definition: LogPager.php:424
LogFormatter\newFromRow
static newFromRow( $row)
Handy shortcut for constructing a formatter directly from database row.
Definition: LogFormatter.php:72
LogPager\getIndexField
getIndexField()
Returns the name of the index field.
Definition: LogPager.php:400
Title\getNamespace
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1034
LogPager\limitFilterTypes
limitFilterTypes()
Definition: LogPager.php:118
LogPage\DELETED_USER
const DELETED_USER
Definition: LogPage.php:40
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2469
MediaWiki\Cache\LinkBatchFactory
Definition: LinkBatchFactory.php:38
IndexPager\$mDb
IDatabase $mDb
Definition: IndexPager.php:100
LogPager\$pattern
bool $pattern
Definition: LogPager.php:43
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
LogPager\$actionRestrictionsEnforced
bool $actionRestrictionsEnforced
Definition: LogPager.php:55
LogEventsList
Definition: LogEventsList.php:31
LogPage\DELETED_ACTION
const DELETED_ACTION
Definition: LogPage.php:38
LogPager\getStartBody
getStartBody()
Hook into getBody(), allows text to be inserted at the start.
Definition: LogPager.php:404
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:617
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:72
ReverseChronologicalPager\$mYear
int $mYear
Definition: ReverseChronologicalPager.php:33
LogEventsList\getExcludeClause
static getExcludeClause( $db, $audience='public', User $user=null)
SQL clause to skip forbidden log types for this user.
Definition: LogEventsList.php:792
LogPager\$mLogEventsList
LogEventsList $mLogEventsList
Definition: LogPager.php:64
LogPager\$typeCGI
string $typeCGI
Definition: LogPager.php:46
LogPager\doQuery
doQuery()
Do the query, using information from the object context.
Definition: LogPager.php:475
LogPager\getAction
getAction()
Definition: LogPager.php:471
LogPager\getTagFilter
getTagFilter()
Definition: LogPager.php:467
LogPager\$mTagFilter
string $mTagFilter
Definition: LogPager.php:61
LogPager\$performer
string $performer
Events limited to those by performer when set.
Definition: LogPager.php:37
LogPager\limitType
limitType( $types)
Set the log reader to return only entries of the given type.
Definition: LogPager.php:160
LogPager\limitPerformer
limitPerformer( $name)
Set the log reader to return only entries by the given user.
Definition: LogPager.php:205
Title
Represents a title within MediaWiki.
Definition: Title.php:41
LogPager\hasEqualsClause
hasEqualsClause( $field)
Checks if $this->mConds has $field matched to a single value.
Definition: LogPager.php:393
LogPager\enforceActionRestrictions
enforceActionRestrictions()
Paranoia: avoid brute force searches (T19342)
Definition: LogPager.php:485
LogPager\getDefaultQuery
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
Definition: LogPager.php:107
ReverseChronologicalPager\$mDay
int $mDay
Definition: ReverseChronologicalPager.php:37
LogPager\enforcePerformerRestrictions
enforcePerformerRestrictions()
Paranoia: avoid brute force searches (T19342)
Definition: LogPager.php:503
NS_USER
const NS_USER
Definition: Defines.php:71
LogPager\getDay
getDay()
Definition: LogPager.php:463
ReverseChronologicalPager
Efficient paging for SQL queries.
Definition: ReverseChronologicalPager.php:29
LogPager\limitAction
limitAction( $action)
Set the log_action field to a specified value (or values)
Definition: LogPager.php:298
IndexPager\getNumRows
getNumRows()
Get the number of rows in the result set.
Definition: IndexPager.php:731
LogPager\limitLogId
limitLogId( $logId)
Limit to the (single) specified log ID.
Definition: LogPager.php:321
LogPager\getYear
getYear()
Definition: LogPager.php:455
$type
$type
Definition: testCompression.php:52