MediaWiki  master
WatchedItemQueryService.php
Go to the documentation of this file.
1 <?php
2 
7 use Wikimedia\Assert\Assert;
10 
22 
23  public const DIR_OLDER = 'older';
24  public const DIR_NEWER = 'newer';
25 
26  public const INCLUDE_FLAGS = 'flags';
27  public const INCLUDE_USER = 'user';
28  public const INCLUDE_USER_ID = 'userid';
29  public const INCLUDE_COMMENT = 'comment';
30  public const INCLUDE_PATROL_INFO = 'patrol';
31  public const INCLUDE_AUTOPATROL_INFO = 'autopatrol';
32  public const INCLUDE_SIZES = 'sizes';
33  public const INCLUDE_LOG_INFO = 'loginfo';
34  public const INCLUDE_TAGS = 'tags';
35 
36  // FILTER_* constants are part of public API (are used in ApiQueryWatchlist and
37  // ApiQueryWatchlistRaw classes) and should not be changed.
38  // Changing values of those constants will result in a breaking change in the API
39  public const FILTER_MINOR = 'minor';
40  public const FILTER_NOT_MINOR = '!minor';
41  public const FILTER_BOT = 'bot';
42  public const FILTER_NOT_BOT = '!bot';
43  public const FILTER_ANON = 'anon';
44  public const FILTER_NOT_ANON = '!anon';
45  public const FILTER_PATROLLED = 'patrolled';
46  public const FILTER_NOT_PATROLLED = '!patrolled';
47  public const FILTER_AUTOPATROLLED = 'autopatrolled';
48  public const FILTER_NOT_AUTOPATROLLED = '!autopatrolled';
49  public const FILTER_UNREAD = 'unread';
50  public const FILTER_NOT_UNREAD = '!unread';
51  public const FILTER_CHANGED = 'changed';
52  public const FILTER_NOT_CHANGED = '!changed';
53 
54  public const SORT_ASC = 'ASC';
55  public const SORT_DESC = 'DESC';
56 
60  private $loadBalancer;
61 
63  private $extensions = null;
64 
66  private $commentStore;
67 
69  private $actorMigration;
70 
73 
76 
77  public function __construct(
83  ) {
84  $this->loadBalancer = $loadBalancer;
85  $this->commentStore = $commentStore;
86  $this->actorMigration = $actorMigration;
87  $this->watchedItemStore = $watchedItemStore;
88  $this->permissionManager = $permissionManager;
89  }
90 
94  private function getExtensions() {
95  if ( $this->extensions === null ) {
96  $this->extensions = [];
97  Hooks::run( 'WatchedItemQueryServiceExtensions', [ &$this->extensions, $this ] );
98  }
99  return $this->extensions;
100  }
101 
105  private function getConnection() {
106  return $this->loadBalancer->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
107  }
108 
153  User $user, array $options = [], &$startFrom = null
154  ) {
155  $options += [
156  'includeFields' => [],
157  'namespaceIds' => [],
158  'filters' => [],
159  'allRevisions' => false,
160  'usedInGenerator' => false
161  ];
162 
163  Assert::parameter(
164  !isset( $options['rcTypes'] )
165  || !array_diff( $options['rcTypes'], [ RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL, RC_CATEGORIZE ] ),
166  '$options[\'rcTypes\']',
167  'must be an array containing only: RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL and/or RC_CATEGORIZE'
168  );
169  Assert::parameter(
170  !isset( $options['dir'] ) || in_array( $options['dir'], [ self::DIR_OLDER, self::DIR_NEWER ] ),
171  '$options[\'dir\']',
172  'must be DIR_OLDER or DIR_NEWER'
173  );
174  Assert::parameter(
175  !isset( $options['start'] ) && !isset( $options['end'] ) && $startFrom === null
176  || isset( $options['dir'] ),
177  '$options[\'dir\']',
178  'must be provided when providing the "start" or "end" options or the $startFrom parameter'
179  );
180  Assert::parameter(
181  !isset( $options['startFrom'] ),
182  '$options[\'startFrom\']',
183  'must not be provided, use $startFrom instead'
184  );
185  Assert::parameter(
186  !isset( $startFrom ) || ( is_array( $startFrom ) && count( $startFrom ) === 2 ),
187  '$startFrom',
188  'must be a two-element array'
189  );
190  if ( array_key_exists( 'watchlistOwner', $options ) ) {
191  Assert::parameterType(
192  User::class,
193  $options['watchlistOwner'],
194  '$options[\'watchlistOwner\']'
195  );
196  Assert::parameter(
197  isset( $options['watchlistOwnerToken'] ),
198  '$options[\'watchlistOwnerToken\']',
199  'must be provided when providing watchlistOwner option'
200  );
201  }
202 
203  $db = $this->getConnection();
204 
205  $tables = $this->getWatchedItemsWithRCInfoQueryTables( $options );
206  $fields = $this->getWatchedItemsWithRCInfoQueryFields( $options );
207  $conds = $this->getWatchedItemsWithRCInfoQueryConds( $db, $user, $options );
208  $dbOptions = $this->getWatchedItemsWithRCInfoQueryDbOptions( $options );
209  $joinConds = $this->getWatchedItemsWithRCInfoQueryJoinConds( $options );
210 
211  if ( $startFrom !== null ) {
212  $conds[] = $this->getStartFromConds( $db, $options, $startFrom );
213  }
214 
215  foreach ( $this->getExtensions() as $extension ) {
216  $extension->modifyWatchedItemsWithRCInfoQuery(
217  $user, $options, $db,
218  $tables,
219  $fields,
220  $conds,
221  $dbOptions,
222  $joinConds
223  );
224  }
225 
226  $res = $db->select(
227  $tables,
228  $fields,
229  $conds,
230  __METHOD__,
231  $dbOptions,
232  $joinConds
233  );
234 
235  $limit = $dbOptions['LIMIT'] ?? INF;
236  $items = [];
237  $startFrom = null;
238  foreach ( $res as $row ) {
239  if ( --$limit <= 0 ) {
240  $startFrom = [ $row->rc_timestamp, $row->rc_id ];
241  break;
242  }
243 
244  $target = new TitleValue( (int)$row->rc_namespace, $row->rc_title );
245  $items[] = [
246  new WatchedItem(
247  $user,
248  $target,
249  $this->watchedItemStore->getLatestNotificationTimestamp(
250  $row->wl_notificationtimestamp, $user, $target
251  )
252  ),
253  $this->getRecentChangeFieldsFromRow( $row )
254  ];
255  }
256 
257  foreach ( $this->getExtensions() as $extension ) {
258  $extension->modifyWatchedItemsWithRCInfo( $user, $options, $db, $items, $res, $startFrom );
259  }
260 
261  return $items;
262  }
263 
283  public function getWatchedItemsForUser( UserIdentity $user, array $options = [] ) {
284  if ( !$user->isRegistered() ) {
285  // TODO: should this just return an empty array or rather complain loud at this point
286  // as e.g. ApiBase::getWatchlistUser does?
287  return [];
288  }
289 
290  $options += [ 'namespaceIds' => [] ];
291 
292  Assert::parameter(
293  !isset( $options['sort'] ) || in_array( $options['sort'], [ self::SORT_ASC, self::SORT_DESC ] ),
294  '$options[\'sort\']',
295  'must be SORT_ASC or SORT_DESC'
296  );
297  Assert::parameter(
298  !isset( $options['filter'] ) || in_array(
299  $options['filter'], [ self::FILTER_CHANGED, self::FILTER_NOT_CHANGED ]
300  ),
301  '$options[\'filter\']',
302  'must be FILTER_CHANGED or FILTER_NOT_CHANGED'
303  );
304  Assert::parameter(
305  !isset( $options['from'] ) && !isset( $options['until'] ) && !isset( $options['startFrom'] )
306  || isset( $options['sort'] ),
307  '$options[\'sort\']',
308  'must be provided if any of "from", "until", "startFrom" options is provided'
309  );
310 
311  $db = $this->getConnection();
312 
313  $conds = $this->getWatchedItemsForUserQueryConds( $db, $user, $options );
314  $dbOptions = $this->getWatchedItemsForUserQueryDbOptions( $options );
315 
316  $res = $db->select(
317  'watchlist',
318  [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
319  $conds,
320  __METHOD__,
321  $dbOptions
322  );
323 
324  $watchedItems = [];
325  foreach ( $res as $row ) {
326  $target = new TitleValue( (int)$row->wl_namespace, $row->wl_title );
327  // todo these could all be cached at some point?
328  $watchedItems[] = new WatchedItem(
329  $user,
330  $target,
331  $this->watchedItemStore->getLatestNotificationTimestamp(
332  $row->wl_notificationtimestamp, $user, $target
333  )
334  );
335  }
336 
337  return $watchedItems;
338  }
339 
340  private function getRecentChangeFieldsFromRow( stdClass $row ) {
341  // FIXME: This can be simplified to single array_filter call filtering by key value,
342  // now we have stopped supporting PHP 5.5
343  $allFields = get_object_vars( $row );
344  $rcKeys = array_filter(
345  array_keys( $allFields ),
346  function ( $key ) {
347  return substr( $key, 0, 3 ) === 'rc_';
348  }
349  );
350  return array_intersect_key( $allFields, array_flip( $rcKeys ) );
351  }
352 
353  private function getWatchedItemsWithRCInfoQueryTables( array $options ) {
354  $tables = [ 'recentchanges', 'watchlist' ];
355  if ( !$options['allRevisions'] ) {
356  $tables[] = 'page';
357  }
358  if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
359  $tables += $this->commentStore->getJoin( 'rc_comment' )['tables'];
360  }
361  if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ||
362  in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ||
363  in_array( self::FILTER_ANON, $options['filters'] ) ||
364  in_array( self::FILTER_NOT_ANON, $options['filters'] ) ||
365  array_key_exists( 'onlyByUser', $options ) || array_key_exists( 'notByUser', $options )
366  ) {
367  $tables += $this->actorMigration->getJoin( 'rc_user' )['tables'];
368  }
369  return $tables;
370  }
371 
372  private function getWatchedItemsWithRCInfoQueryFields( array $options ) {
373  $fields = [
374  'rc_id',
375  'rc_namespace',
376  'rc_title',
377  'rc_timestamp',
378  'rc_type',
379  'rc_deleted',
380  'wl_notificationtimestamp'
381  ];
382 
383  $rcIdFields = [
384  'rc_cur_id',
385  'rc_this_oldid',
386  'rc_last_oldid',
387  ];
388  if ( $options['usedInGenerator'] ) {
389  if ( $options['allRevisions'] ) {
390  $rcIdFields = [ 'rc_this_oldid' ];
391  } else {
392  $rcIdFields = [ 'rc_cur_id' ];
393  }
394  }
395  $fields = array_merge( $fields, $rcIdFields );
396 
397  if ( in_array( self::INCLUDE_FLAGS, $options['includeFields'] ) ) {
398  $fields = array_merge( $fields, [ 'rc_type', 'rc_minor', 'rc_bot' ] );
399  }
400  if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ) {
401  $fields['rc_user_text'] = $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user_text'];
402  }
403  if ( in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ) {
404  $fields['rc_user'] = $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user'];
405  }
406  if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
407  $fields += $this->commentStore->getJoin( 'rc_comment' )['fields'];
408  }
409  if ( in_array( self::INCLUDE_PATROL_INFO, $options['includeFields'] ) ) {
410  $fields = array_merge( $fields, [ 'rc_patrolled', 'rc_log_type' ] );
411  }
412  if ( in_array( self::INCLUDE_SIZES, $options['includeFields'] ) ) {
413  $fields = array_merge( $fields, [ 'rc_old_len', 'rc_new_len' ] );
414  }
415  if ( in_array( self::INCLUDE_LOG_INFO, $options['includeFields'] ) ) {
416  $fields = array_merge( $fields, [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ] );
417  }
418  if ( in_array( self::INCLUDE_TAGS, $options['includeFields'] ) ) {
419  // prefixed with rc_ to include the field in getRecentChangeFieldsFromRow
420  $fields['rc_tags'] = ChangeTags::makeTagSummarySubquery( 'recentchanges' );
421  }
422 
423  return $fields;
424  }
425 
427  IDatabase $db,
428  User $user,
429  array $options
430  ) {
431  $watchlistOwnerId = $this->getWatchlistOwnerId( $user, $options );
432  $conds = [ 'wl_user' => $watchlistOwnerId ];
433 
434  if ( !$options['allRevisions'] ) {
435  $conds[] = $db->makeList(
436  [ 'rc_this_oldid=page_latest', 'rc_type=' . RC_LOG ],
437  LIST_OR
438  );
439  }
440 
441  if ( $options['namespaceIds'] ) {
442  $conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
443  }
444 
445  if ( array_key_exists( 'rcTypes', $options ) ) {
446  $conds['rc_type'] = array_map( 'intval', $options['rcTypes'] );
447  }
448 
449  $conds = array_merge(
450  $conds,
451  $this->getWatchedItemsWithRCInfoQueryFilterConds( $user, $options )
452  );
453 
454  $conds = array_merge( $conds, $this->getStartEndConds( $db, $options ) );
455 
456  if ( !isset( $options['start'] ) && !isset( $options['end'] ) && $db->getType() === 'mysql' ) {
457  // This is an index optimization for mysql
458  $conds[] = 'rc_timestamp > ' . $db->addQuotes( '' );
459  }
460 
461  $conds = array_merge( $conds, $this->getUserRelatedConds( $db, $user, $options ) );
462 
463  $deletedPageLogCond = $this->getExtraDeletedPageLogEntryRelatedCond( $db, $user );
464  if ( $deletedPageLogCond ) {
465  $conds[] = $deletedPageLogCond;
466  }
467 
468  return $conds;
469  }
470 
471  private function getWatchlistOwnerId( UserIdentity $user, array $options ) {
472  if ( array_key_exists( 'watchlistOwner', $options ) ) {
474  $watchlistOwner = $options['watchlistOwner'];
475  $ownersToken =
476  $watchlistOwner->getOption( 'watchlisttoken' );
477  $token = $options['watchlistOwnerToken'];
478  if ( $ownersToken == '' || !hash_equals( $ownersToken, $token ) ) {
479  throw ApiUsageException::newWithMessage( null, 'apierror-bad-watchlist-token', 'bad_wltoken' );
480  }
481  return $watchlistOwner->getId();
482  }
483  return $user->getId();
484  }
485 
486  private function getWatchedItemsWithRCInfoQueryFilterConds( User $user, array $options ) {
487  $conds = [];
488 
489  if ( in_array( self::FILTER_MINOR, $options['filters'] ) ) {
490  $conds[] = 'rc_minor != 0';
491  } elseif ( in_array( self::FILTER_NOT_MINOR, $options['filters'] ) ) {
492  $conds[] = 'rc_minor = 0';
493  }
494 
495  if ( in_array( self::FILTER_BOT, $options['filters'] ) ) {
496  $conds[] = 'rc_bot != 0';
497  } elseif ( in_array( self::FILTER_NOT_BOT, $options['filters'] ) ) {
498  $conds[] = 'rc_bot = 0';
499  }
500 
501  if ( in_array( self::FILTER_ANON, $options['filters'] ) ) {
502  $conds[] = $this->actorMigration->isAnon(
503  $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user']
504  );
505  } elseif ( in_array( self::FILTER_NOT_ANON, $options['filters'] ) ) {
506  $conds[] = $this->actorMigration->isNotAnon(
507  $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user']
508  );
509  }
510 
511  if ( $user->useRCPatrol() || $user->useNPPatrol() ) {
512  // TODO: not sure if this should simply ignore patrolled filters if user does not have the patrol
513  // right, or maybe rather fail loud at this point, same as e.g. ApiQueryWatchlist does?
514  if ( in_array( self::FILTER_PATROLLED, $options['filters'] ) ) {
515  $conds[] = 'rc_patrolled != ' . RecentChange::PRC_UNPATROLLED;
516  } elseif ( in_array( self::FILTER_NOT_PATROLLED, $options['filters'] ) ) {
517  $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
518  }
519 
520  if ( in_array( self::FILTER_AUTOPATROLLED, $options['filters'] ) ) {
521  $conds['rc_patrolled'] = RecentChange::PRC_AUTOPATROLLED;
522  } elseif ( in_array( self::FILTER_NOT_AUTOPATROLLED, $options['filters'] ) ) {
523  $conds[] = 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED;
524  }
525  }
526 
527  if ( in_array( self::FILTER_UNREAD, $options['filters'] ) ) {
528  $conds[] = 'rc_timestamp >= wl_notificationtimestamp';
529  } elseif ( in_array( self::FILTER_NOT_UNREAD, $options['filters'] ) ) {
530  // TODO: should this be changed to use Database::makeList?
531  $conds[] = 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp';
532  }
533 
534  return $conds;
535  }
536 
537  private function getStartEndConds( IDatabase $db, array $options ) {
538  if ( !isset( $options['start'] ) && !isset( $options['end'] ) ) {
539  return [];
540  }
541 
542  $conds = [];
543 
544  if ( isset( $options['start'] ) ) {
545  $after = $options['dir'] === self::DIR_OLDER ? '<=' : '>=';
546  $conds[] = 'rc_timestamp ' . $after . ' ' .
547  $db->addQuotes( $db->timestamp( $options['start'] ) );
548  }
549  if ( isset( $options['end'] ) ) {
550  $before = $options['dir'] === self::DIR_OLDER ? '>=' : '<=';
551  $conds[] = 'rc_timestamp ' . $before . ' ' .
552  $db->addQuotes( $db->timestamp( $options['end'] ) );
553  }
554 
555  return $conds;
556  }
557 
558  private function getUserRelatedConds( IDatabase $db, UserIdentity $user, array $options ) {
559  if ( !array_key_exists( 'onlyByUser', $options ) && !array_key_exists( 'notByUser', $options ) ) {
560  return [];
561  }
562 
563  $conds = [];
564 
565  if ( array_key_exists( 'onlyByUser', $options ) ) {
566  $byUser = User::newFromName( $options['onlyByUser'], false );
567  $conds[] = $this->actorMigration->getWhere( $db, 'rc_user', $byUser )['conds'];
568  } elseif ( array_key_exists( 'notByUser', $options ) ) {
569  $byUser = User::newFromName( $options['notByUser'], false );
570  $conds[] = 'NOT(' . $this->actorMigration->getWhere( $db, 'rc_user', $byUser )['conds'] . ')';
571  }
572 
573  // Avoid brute force searches (T19342)
574  $bitmask = 0;
575  if ( !$this->permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
576  $bitmask = RevisionRecord::DELETED_USER;
577  } elseif ( !$this->permissionManager
578  ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
579  ) {
580  $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
581  }
582  if ( $bitmask ) {
583  $conds[] = $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask";
584  }
585 
586  return $conds;
587  }
588 
590  // LogPage::DELETED_ACTION hides the affected page, too. So hide those
591  // entirely from the watchlist, or someone could guess the title.
592  $bitmask = 0;
593  if ( !$this->permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
594  $bitmask = LogPage::DELETED_ACTION;
595  } elseif ( !$this->permissionManager
596  ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
597  ) {
599  }
600  if ( $bitmask ) {
601  return $db->makeList( [
602  'rc_type != ' . RC_LOG,
603  $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
604  ], LIST_OR );
605  }
606  return '';
607  }
608 
609  private function getStartFromConds( IDatabase $db, array $options, array $startFrom ) {
610  $op = $options['dir'] === self::DIR_OLDER ? '<' : '>';
611  list( $rcTimestamp, $rcId ) = $startFrom;
612  $rcTimestamp = $db->addQuotes( $db->timestamp( $rcTimestamp ) );
613  $rcId = (int)$rcId;
614  return $db->makeList(
615  [
616  "rc_timestamp $op $rcTimestamp",
617  $db->makeList(
618  [
619  "rc_timestamp = $rcTimestamp",
620  "rc_id $op= $rcId"
621  ],
622  LIST_AND
623  )
624  ],
625  LIST_OR
626  );
627  }
628 
630  IDatabase $db, UserIdentity $user, array $options
631  ) {
632  $conds = [ 'wl_user' => $user->getId() ];
633  if ( $options['namespaceIds'] ) {
634  $conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
635  }
636  if ( isset( $options['filter'] ) ) {
637  $filter = $options['filter'];
638  if ( $filter === self::FILTER_CHANGED ) {
639  $conds[] = 'wl_notificationtimestamp IS NOT NULL';
640  } else {
641  $conds[] = 'wl_notificationtimestamp IS NULL';
642  }
643  }
644 
645  if ( isset( $options['from'] ) ) {
646  $op = $options['sort'] === self::SORT_ASC ? '>' : '<';
647  $conds[] = $this->getFromUntilTargetConds( $db, $options['from'], $op );
648  }
649  if ( isset( $options['until'] ) ) {
650  $op = $options['sort'] === self::SORT_ASC ? '<' : '>';
651  $conds[] = $this->getFromUntilTargetConds( $db, $options['until'], $op );
652  }
653  if ( isset( $options['startFrom'] ) ) {
654  $op = $options['sort'] === self::SORT_ASC ? '>' : '<';
655  $conds[] = $this->getFromUntilTargetConds( $db, $options['startFrom'], $op );
656  }
657 
658  return $conds;
659  }
660 
670  private function getFromUntilTargetConds( IDatabase $db, LinkTarget $target, $op ) {
671  return $db->makeList(
672  [
673  "wl_namespace $op " . $target->getNamespace(),
674  $db->makeList(
675  [
676  'wl_namespace = ' . $target->getNamespace(),
677  "wl_title $op= " . $db->addQuotes( $target->getDBkey() )
678  ],
679  LIST_AND
680  )
681  ],
682  LIST_OR
683  );
684  }
685 
686  private function getWatchedItemsWithRCInfoQueryDbOptions( array $options ) {
687  $dbOptions = [];
688 
689  if ( array_key_exists( 'dir', $options ) ) {
690  $sort = $options['dir'] === self::DIR_OLDER ? ' DESC' : '';
691  $dbOptions['ORDER BY'] = [ 'rc_timestamp' . $sort, 'rc_id' . $sort ];
692  }
693 
694  if ( array_key_exists( 'limit', $options ) ) {
695  $dbOptions['LIMIT'] = (int)$options['limit'] + 1;
696  }
697 
698  return $dbOptions;
699  }
700 
701  private function getWatchedItemsForUserQueryDbOptions( array $options ) {
702  $dbOptions = [];
703  if ( array_key_exists( 'sort', $options ) ) {
704  $dbOptions['ORDER BY'] = [
705  "wl_namespace {$options['sort']}",
706  "wl_title {$options['sort']}"
707  ];
708  if ( count( $options['namespaceIds'] ) === 1 ) {
709  $dbOptions['ORDER BY'] = "wl_title {$options['sort']}";
710  }
711  }
712  if ( array_key_exists( 'limit', $options ) ) {
713  $dbOptions['LIMIT'] = (int)$options['limit'];
714  }
715  return $dbOptions;
716  }
717 
718  private function getWatchedItemsWithRCInfoQueryJoinConds( array $options ) {
719  $joinConds = [
720  'watchlist' => [ 'JOIN',
721  [
722  'wl_namespace=rc_namespace',
723  'wl_title=rc_title'
724  ]
725  ]
726  ];
727  if ( !$options['allRevisions'] ) {
728  $joinConds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
729  }
730  if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
731  $joinConds += $this->commentStore->getJoin( 'rc_comment' )['joins'];
732  }
733  if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ||
734  in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ||
735  in_array( self::FILTER_ANON, $options['filters'] ) ||
736  in_array( self::FILTER_NOT_ANON, $options['filters'] ) ||
737  array_key_exists( 'onlyByUser', $options ) || array_key_exists( 'notByUser', $options )
738  ) {
739  $joinConds += $this->actorMigration->getJoin( 'rc_user' )['joins'];
740  }
741  return $joinConds;
742  }
743 
744 }
WatchedItemQueryService\getConnection
getConnection()
Definition: WatchedItemQueryService.php:105
ChangeTags\makeTagSummarySubquery
static makeTagSummarySubquery( $tables)
Make the tag summary subquery based on the given tables and return it.
Definition: ChangeTags.php:841
WatchedItemQueryService\getFromUntilTargetConds
getFromUntilTargetConds(IDatabase $db, LinkTarget $target, $op)
Creates a query condition part for getting only items before or after the given link target (while or...
Definition: WatchedItemQueryService.php:670
RC_EXTERNAL
const RC_EXTERNAL
Definition: Defines.php:125
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryConds
getWatchedItemsWithRCInfoQueryConds(IDatabase $db, User $user, array $options)
Definition: WatchedItemQueryService.php:426
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryJoinConds
getWatchedItemsWithRCInfoQueryJoinConds(array $options)
Definition: WatchedItemQueryService.php:718
WatchedItemQueryServiceExtension
Definition: WatchedItemQueryServiceExtension.php:17
WatchedItemQueryService\$commentStore
CommentStore $commentStore
Definition: WatchedItemQueryService.php:66
WatchedItemQueryService\INCLUDE_COMMENT
const INCLUDE_COMMENT
Definition: WatchedItemQueryService.php:29
WatchedItemQueryService\FILTER_NOT_AUTOPATROLLED
const FILTER_NOT_AUTOPATROLLED
Definition: WatchedItemQueryService.php:48
WatchedItemQueryService\FILTER_PATROLLED
const FILTER_PATROLLED
Definition: WatchedItemQueryService.php:45
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryFields
getWatchedItemsWithRCInfoQueryFields(array $options)
Definition: WatchedItemQueryService.php:372
WatchedItemQueryService\getStartEndConds
getStartEndConds(IDatabase $db, array $options)
Definition: WatchedItemQueryService.php:537
RC_LOG
const RC_LOG
Definition: Defines.php:124
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryDbOptions
getWatchedItemsWithRCInfoQueryDbOptions(array $options)
Definition: WatchedItemQueryService.php:686
CommentStore
CommentStore handles storage of comments (edit summaries, log reasons, etc) in the database.
Definition: CommentStore.php:31
RC_EDIT
const RC_EDIT
Definition: Defines.php:122
WatchedItemQueryService\getWatchedItemsForUserQueryDbOptions
getWatchedItemsForUserQueryDbOptions(array $options)
Definition: WatchedItemQueryService.php:701
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:536
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3675
WatchedItemQueryService\getWatchedItemsWithRecentChangeInfo
getWatchedItemsWithRecentChangeInfo(User $user, array $options=[], &$startFrom=null)
Definition: WatchedItemQueryService.php:152
ActorMigration
This class handles the logic for the actor table migration.
Definition: ActorMigration.php:38
ApiUsageException\newWithMessage
static newWithMessage(?ApiBase $module, $msg, $code=null, $data=null, $httpCode=0)
Definition: ApiUsageException.php:63
$res
$res
Definition: testCompression.php:54
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3666
WatchedItemQueryService\$actorMigration
ActorMigration $actorMigration
Definition: WatchedItemQueryService.php:69
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
LIST_AND
const LIST_AND
Definition: Defines.php:39
WatchedItemQueryService\INCLUDE_TAGS
const INCLUDE_TAGS
Definition: WatchedItemQueryService.php:34
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
WatchedItemQueryService\getWatchlistOwnerId
getWatchlistOwnerId(UserIdentity $user, array $options)
Definition: WatchedItemQueryService.php:471
WatchedItemQueryService\getWatchedItemsForUserQueryConds
getWatchedItemsForUserQueryConds(IDatabase $db, UserIdentity $user, array $options)
Definition: WatchedItemQueryService.php:629
WatchedItemQueryService\INCLUDE_LOG_INFO
const INCLUDE_LOG_INFO
Definition: WatchedItemQueryService.php:33
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
Wikimedia\Rdbms\IDatabase\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
LIST_OR
const LIST_OR
Definition: Defines.php:42
WatchedItemQueryService\$watchedItemStore
WatchedItemStoreInterface $watchedItemStore
Definition: WatchedItemQueryService.php:72
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryFilterConds
getWatchedItemsWithRCInfoQueryFilterConds(User $user, array $options)
Definition: WatchedItemQueryService.php:486
MediaWiki\User\UserIdentity\isRegistered
isRegistered()
WatchedItemQueryService\FILTER_MINOR
const FILTER_MINOR
Definition: WatchedItemQueryService.php:39
WatchedItemQueryService\FILTER_NOT_CHANGED
const FILTER_NOT_CHANGED
Definition: WatchedItemQueryService.php:52
WatchedItemQueryService\INCLUDE_AUTOPATROL_INFO
const INCLUDE_AUTOPATROL_INFO
Definition: WatchedItemQueryService.php:31
WatchedItemQueryService\INCLUDE_USER
const INCLUDE_USER
Definition: WatchedItemQueryService.php:27
WatchedItemQueryService\FILTER_NOT_BOT
const FILTER_NOT_BOT
Definition: WatchedItemQueryService.php:42
WatchedItemQueryService
Definition: WatchedItemQueryService.php:21
WatchedItemQueryService\INCLUDE_PATROL_INFO
const INCLUDE_PATROL_INFO
Definition: WatchedItemQueryService.php:30
WatchedItemQueryService\$permissionManager
PermissionManager $permissionManager
Definition: WatchedItemQueryService.php:75
WatchedItemQueryService\getWatchedItemsForUser
getWatchedItemsForUser(UserIdentity $user, array $options=[])
For simple listing of user's watchlist items, see WatchedItemStore::getWatchedItemsForUser.
Definition: WatchedItemQueryService.php:283
WatchedItemQueryService\__construct
__construct(ILoadBalancer $loadBalancer, CommentStore $commentStore, ActorMigration $actorMigration, WatchedItemStoreInterface $watchedItemStore, PermissionManager $permissionManager)
Definition: WatchedItemQueryService.php:77
WatchedItemQueryService\FILTER_BOT
const FILTER_BOT
Definition: WatchedItemQueryService.php:41
WatchedItemQueryService\INCLUDE_USER_ID
const INCLUDE_USER_ID
Definition: WatchedItemQueryService.php:28
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
LogPage\DELETED_ACTION
const DELETED_ACTION
Definition: LogPage.php:34
WatchedItemQueryService\$loadBalancer
ILoadBalancer $loadBalancer
Definition: WatchedItemQueryService.php:60
WatchedItemQueryService\DIR_NEWER
const DIR_NEWER
Definition: WatchedItemQueryService.php:24
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:48
WatchedItemQueryService\SORT_DESC
const SORT_DESC
Definition: WatchedItemQueryService.php:55
WatchedItemQueryService\getRecentChangeFieldsFromRow
getRecentChangeFieldsFromRow(stdClass $row)
Definition: WatchedItemQueryService.php:340
WatchedItem
Representation of a pair of user and title for watchlist entries.
Definition: WatchedItem.php:33
WatchedItemQueryService\FILTER_NOT_PATROLLED
const FILTER_NOT_PATROLLED
Definition: WatchedItemQueryService.php:46
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
WatchedItemQueryService\SORT_ASC
const SORT_ASC
Definition: WatchedItemQueryService.php:54
WatchedItemQueryService\FILTER_NOT_MINOR
const FILTER_NOT_MINOR
Definition: WatchedItemQueryService.php:40
RC_NEW
const RC_NEW
Definition: Defines.php:123
WatchedItemQueryService\INCLUDE_FLAGS
const INCLUDE_FLAGS
Definition: WatchedItemQueryService.php:26
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:81
Wikimedia\Rdbms\IDatabase\bitAnd
bitAnd( $fieldLeft, $fieldRight)
WatchedItemQueryService\DIR_OLDER
const DIR_OLDER
Definition: WatchedItemQueryService.php:23
WatchedItemQueryService\getUserRelatedConds
getUserRelatedConds(IDatabase $db, UserIdentity $user, array $options)
Definition: WatchedItemQueryService.php:558
WatchedItemQueryService\getExtensions
getExtensions()
Definition: WatchedItemQueryService.php:94
MediaWiki\User\UserIdentity\getId
getId()
Wikimedia\Rdbms\IDatabase\addQuotes
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.
WatchedItemQueryService\FILTER_CHANGED
const FILTER_CHANGED
Definition: WatchedItemQueryService.php:51
WatchedItemQueryService\FILTER_AUTOPATROLLED
const FILTER_AUTOPATROLLED
Definition: WatchedItemQueryService.php:47
WatchedItemQueryService\FILTER_NOT_UNREAD
const FILTER_NOT_UNREAD
Definition: WatchedItemQueryService.php:50
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:79
WatchedItemQueryService\FILTER_NOT_ANON
const FILTER_NOT_ANON
Definition: WatchedItemQueryService.php:44
Wikimedia\Rdbms\IDatabase\getType
getType()
Get the type of the DBMS (e.g.
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryTables
getWatchedItemsWithRCInfoQueryTables(array $options)
Definition: WatchedItemQueryService.php:353
LogPage\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: LogPage.php:37
RC_CATEGORIZE
const RC_CATEGORIZE
Definition: Defines.php:126
WatchedItemQueryService\INCLUDE_SIZES
const INCLUDE_SIZES
Definition: WatchedItemQueryService.php:32
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
WatchedItemQueryService\getExtraDeletedPageLogEntryRelatedCond
getExtraDeletedPageLogEntryRelatedCond(IDatabase $db, UserIdentity $user)
Definition: WatchedItemQueryService.php:589
WatchedItemStoreInterface
Definition: WatchedItemStoreInterface.php:30
WatchedItemQueryService\FILTER_ANON
const FILTER_ANON
Definition: WatchedItemQueryService.php:43
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
WatchedItemQueryService\FILTER_UNREAD
const FILTER_UNREAD
Definition: WatchedItemQueryService.php:49
WatchedItemQueryService\$extensions
WatchedItemQueryServiceExtension[] null $extensions
Definition: WatchedItemQueryService.php:63
Wikimedia\Rdbms\IDatabase\makeList
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
TitleValue
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:37
WatchedItemQueryService\getStartFromConds
getStartFromConds(IDatabase $db, array $options, array $startFrom)
Definition: WatchedItemQueryService.php:609