51 private $performer =
'';
60 private $pattern =
false;
63 private $typeCGI =
'';
69 private $performerRestrictionsEnforced =
false;
72 private $actionRestrictionsEnforced =
false;
87 private $linkBatchFactory;
90 private $actorNormalization;
111 public function __construct( $list, $types = [], $performer =
'', $pages =
'',
112 $pattern =
false, $conds = [], $year =
false, $month =
false, $day =
false,
113 $tagFilter =
'', $action =
'', $logId = 0,
119 parent::__construct( $list->getContext() );
122 $this->mConds = $conds;
123 $this->mLogEventsList = $list;
126 $this->linkBatchFactory = $linkBatchFactory ?? $services->getLinkBatchFactory();
127 $this->actorNormalization = $actorNormalization ?? $services->getActorNormalization();
128 $this->logFormatterFactory = $logFormatterFactory ?? $services->getLogFormatterFactory();
131 $this->limitType( $types );
132 $this->limitFilterTypes();
133 $this->limitPerformer( $performer );
134 $this->limitTitles( $pages, $pattern );
135 $this->limitAction( $action );
137 $this->mTagFilter = (string)$tagFilter;
138 $this->mTagInvert = (bool)$tagInvert;
142 $query = parent::getDefaultQuery();
143 $query[
'type'] = $this->typeCGI;
144 $query[
'user'] = $this->performer;
152 private function limitFilterTypes() {
158 foreach ( $filterTypes as $type => $hide ) {
160 $excludeTypes[] = $type;
163 if ( $excludeTypes ) {
164 $this->mConds[] = $this->mDb->expr(
'log_type',
'!=', $excludeTypes );
170 if ( count( $this->types ) ) {
175 $wpfilters = $this->
getRequest()->getArray(
"wpfilters" );
178 foreach ( $filterLogTypes as $type => $default ) {
180 if ( $wpfilters ===
null ) {
181 $hide = $this->
getRequest()->getBool(
"hide_{$type}_log", $default );
183 $hide = !in_array( $type, $wpfilters );
186 $filters[$type] = $hide;
199 private function limitType( $types ) {
202 $types = ( $types ===
'' ) ? [] : (array)$types;
204 $needReindex =
false;
205 foreach ( $types as $type ) {
206 if ( isset( $restrictions[$type] )
207 && !$this->
getAuthority()->isAllowed( $restrictions[$type] )
210 $types = array_diff( $types, [ $type ] );
213 if ( $needReindex ) {
216 $types = array_values( $types );
218 $this->types = $types;
224 $audience = ( $types || $this->
hasEqualsClause(
'log_id' ) ) ?
'user' :
'public';
225 $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience, $this->
getAuthority() );
226 if ( $hideLogs !==
false ) {
227 $this->mConds[] = $hideLogs;
229 if ( count( $types ) ) {
230 $this->mConds[
'log_type'] = $types;
232 if ( count( $types ) == 1 ) {
233 $this->typeCGI = $types[0];
244 private function limitPerformer( $name ) {
249 $actorId = $this->actorNormalization->findActorIdByName( $name, $this->mDb );
253 $this->mConds[] =
'1 = 0';
257 $this->mConds[
'log_actor' ] = $actorId;
259 $this->enforcePerformerRestrictions();
261 $this->performer = $name;
272 private function limitTitles( $pages, $pattern ) {
273 if ( !is_array( $pages ) ) {
278 foreach ( $pages as $page ) {
279 if ( (
string)$page ===
'' ) {
282 if ( !$page instanceof PageReference ) {
284 $page = Title::newFromText( $page );
290 $this->page = $titleFormatter->getPrefixedDBkey( $page );
291 $orConds[] = $this->mDb->makeList(
292 $this->getTitleConds( $page, $pattern ),
293 ISQLPlatform::LIST_AND
297 $this->mConds[] = $this->mDb->makeList( $orConds, ISQLPlatform::LIST_OR );
298 $this->pattern = $pattern;
299 $this->enforceActionRestrictions();
310 private function getTitleConds( $page, $pattern ) {
312 $ns = $page->getNamespace();
317 $doUserRightsLogLike =
false;
318 if ( $this->types == [
'rights' ] ) {
319 $parts = explode( $interwikiDelimiter, $page->getDBkey() );
320 if ( count( $parts ) == 2 ) {
321 [ $name, $database ] = array_map(
'trim', $parts );
322 if ( str_contains( $database,
'*' ) ) {
323 $doUserRightsLogLike =
true;
341 $conds[
'log_namespace'] = $ns;
342 if ( $doUserRightsLogLike ) {
344 $params = [ $name . $interwikiDelimiter ];
347 $databaseParts = explode(
'*', $database );
348 $databasePartCount = count( $databaseParts );
349 foreach ( $databaseParts as $i => $databasepart ) {
351 if ( $i < $databasePartCount - 1 ) {
355 $conds[] = $db->expr(
'log_title', IExpression::LIKE,
new LikeValue( ...$params ) );
356 } elseif ( $pattern ) {
357 $conds[] = $db->expr(
360 new LikeValue( $page->getDBkey(), $db->anyString() )
363 $conds[
'log_title'] = $page->getDBkey();
373 private function limitAction( $action ) {
375 $type = $this->typeCGI;
376 if ( $type ===
'' ) {
381 if ( isset( $actions[$type] ) ) {
383 if ( $action !==
'' && isset( $actions[$type][$action] ) ) {
385 $this->mConds[
'log_action'] = $actions[$type][$action];
386 $this->action = $action;
399 $this->mConds[
'log_id'] = $logId;
408 $queryBuilder = DatabaseLogEntry::newSelectQueryBuilder( $this->mDb )
409 ->where( $this->mConds );
411 # Add log_search table if there are conditions on it.
412 # This filters the results to only include log rows that have
413 # log_search records with the specified ls_field and ls_value values.
414 if ( array_key_exists(
'ls_field', $this->mConds ) ) {
415 $queryBuilder->join(
'log_search',
null,
'ls_log_id=log_id' );
416 $queryBuilder->ignoreIndex( [
'log_search' =>
'ls_log_id' ] );
417 $queryBuilder->useIndex( [
'logging' =>
'PRIMARY' ] );
421 # Since (ls_field,ls_value,ls_logid) is unique, if the condition is
422 # to match a specific (ls_field,ls_value) tuple, then there will be
423 # no duplicate log rows. Otherwise, we need to remove the duplicates.
424 $queryBuilder->distinct();
426 } elseif ( array_key_exists(
'log_actor', $this->mConds ) ) {
428 $index =
'log_actor_time';
431 $index =
'log_actor_type_time';
435 $queryBuilder->useIndex( [
'logging' => $index ] );
442 if ( $this->mTagFilter ===
'' && !array_key_exists(
'ls_field', $this->mConds ) ) {
443 $queryBuilder->straightJoinOption();
447 if ( $maxExecTime ) {
448 $queryBuilder->setMaxExecutionTime( $maxExecTime );
451 # Add ChangeTags filter query
459 return $queryBuilder->getQueryInfo();
469 array_key_exists( $field, $this->mConds ) &&
470 ( !is_array( $this->mConds[$field] ) || count( $this->mConds[$field] ) == 1 )
475 return [ [
'log_timestamp',
'log_id' ] ];
479 $lb = $this->linkBatchFactory->newLinkBatch()->setCaller( __METHOD__ );
480 foreach ( $this->mResult as $row ) {
481 $lb->add( $row->log_namespace, $row->log_title );
482 $lb->addUser(
new UserIdentityValue( (
int)$row->log_user, $row->log_user_text ) );
483 $formatter = $this->logFormatterFactory->newFromRow( $row );
484 foreach ( $formatter->getPreloadTitles() as $title ) {
485 $lb->addObj( $title );
492 return $this->mLogEventsList->logLine( $row );
510 return $this->performer;
528 return $this->pattern;
563 return $this->mTagFilter;
572 return $this->mTagInvert;
581 return $this->action;
587 private function enforceActionRestrictions() {
588 if ( $this->actionRestrictionsEnforced ) {
591 $this->actionRestrictionsEnforced =
true;
592 if ( !$this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
593 $this->mConds[] = $this->mDb->bitAnd(
'log_deleted', LogPage::DELETED_ACTION ) .
' = 0';
594 } elseif ( !$this->
getAuthority()->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
595 $this->mConds[] = $this->mDb->bitAnd(
'log_deleted', LogPage::SUPPRESSED_ACTION ) .
596 ' != ' . LogPage::SUPPRESSED_ACTION;
603 private function enforcePerformerRestrictions() {
605 if ( $this->performerRestrictionsEnforced ) {
608 $this->performerRestrictionsEnforced =
true;
609 if ( !$this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
610 $this->mConds[] = $this->mDb->bitAnd(
'log_deleted', LogPage::DELETED_USER ) .
' = 0';
611 } elseif ( !$this->
getAuthority()->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
612 $this->mConds[] = $this->mDb->bitAnd(
'log_deleted', LogPage::SUPPRESSED_USER ) .
613 ' != ' . LogPage::SUPPRESSED_USER;