5 use Wikimedia\Assert\Assert;
82 return $this->loadBalancer->getConnectionRef(
DB_REPLICA, [
'watchlist' ] );
86 if ( !$this->commentStore ) {
139 'includeFields' => [],
140 'namespaceIds' => [],
142 'allRevisions' =>
false,
143 'usedInGenerator' =>
false
149 '$options[\'rcTypes\']',
150 'must be an array containing only: RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL and/or RC_CATEGORIZE'
153 !isset(
$options[
'dir'] ) || in_array(
$options[
'dir'], [ self::DIR_OLDER, self::DIR_NEWER ] ),
155 'must be DIR_OLDER or DIR_NEWER'
158 !isset(
$options[
'start'] ) && !isset(
$options[
'end'] ) && $startFrom ===
null
161 'must be provided when providing the "start" or "end" options or the $startFrom parameter'
165 '$options[\'startFrom\']',
166 'must not be provided, use $startFrom instead'
169 !isset( $startFrom ) || ( is_array( $startFrom ) &&
count( $startFrom ) === 2 ),
171 'must be a two-element array'
173 if ( array_key_exists(
'watchlistOwner',
$options ) ) {
174 Assert::parameterType(
177 '$options[\'watchlistOwner\']'
180 isset(
$options[
'watchlistOwnerToken'] ),
181 '$options[\'watchlistOwnerToken\']',
182 'must be provided when providing watchlistOwner option'
194 if ( $startFrom !==
null ) {
199 $extension->modifyWatchedItemsWithRCInfoQuery(
218 $limit = isset( $dbOptions[
'LIMIT'] ) ? $dbOptions[
'LIMIT'] : INF;
221 foreach (
$res as $row ) {
222 if ( --$limit <= 0 ) {
223 $startFrom = [ $row->rc_timestamp, $row->rc_id ];
230 new TitleValue( (
int)$row->rc_namespace, $row->rc_title ),
231 $row->wl_notificationtimestamp
238 $extension->modifyWatchedItemsWithRCInfo(
$user,
$options, $db, $items,
$res, $startFrom );
264 if (
$user->isAnon() ) {
270 $options += [
'namespaceIds' => [] ];
273 !isset(
$options[
'sort'] ) || in_array(
$options[
'sort'], [ self::SORT_ASC, self::SORT_DESC ] ),
274 '$options[\'sort\']',
275 'must be SORT_ASC or SORT_DESC'
278 !isset(
$options[
'filter'] ) || in_array(
279 $options[
'filter'], [ self::FILTER_CHANGED, self::FILTER_NOT_CHANGED ]
281 '$options[\'filter\']',
282 'must be FILTER_CHANGED or FILTER_NOT_CHANGED'
287 '$options[\'sort\']',
288 'must be provided if any of "from", "until", "startFrom" options is provided'
298 [
'wl_namespace',
'wl_title',
'wl_notificationtimestamp' ],
305 foreach (
$res as $row ) {
309 new TitleValue( (
int)$row->wl_namespace, $row->wl_title ),
310 $row->wl_notificationtimestamp
314 return $watchedItems;
320 $allFields = get_object_vars( $row );
321 $rcKeys = array_filter(
322 array_keys( $allFields ),
324 return substr( $key, 0, 3 ) ===
'rc_';
327 return array_intersect_key( $allFields, array_flip( $rcKeys ) );
331 $tables = [
'recentchanges',
'watchlist' ];
335 if ( in_array( self::INCLUDE_COMMENT,
$options[
'includeFields'] ) ) {
349 'wl_notificationtimestamp'
357 if (
$options[
'usedInGenerator'] ) {
359 $rcIdFields = [
'rc_this_oldid' ];
361 $rcIdFields = [
'rc_cur_id' ];
364 $fields = array_merge( $fields, $rcIdFields );
366 if ( in_array( self::INCLUDE_FLAGS,
$options[
'includeFields'] ) ) {
367 $fields = array_merge( $fields, [
'rc_type',
'rc_minor',
'rc_bot' ] );
369 if ( in_array( self::INCLUDE_USER,
$options[
'includeFields'] ) ) {
370 $fields[] =
'rc_user_text';
372 if ( in_array( self::INCLUDE_USER_ID,
$options[
'includeFields'] ) ) {
373 $fields[] =
'rc_user';
375 if ( in_array( self::INCLUDE_COMMENT,
$options[
'includeFields'] ) ) {
378 if ( in_array( self::INCLUDE_PATROL_INFO,
$options[
'includeFields'] ) ) {
379 $fields = array_merge( $fields, [
'rc_patrolled',
'rc_log_type' ] );
381 if ( in_array( self::INCLUDE_SIZES,
$options[
'includeFields'] ) ) {
382 $fields = array_merge( $fields, [
'rc_old_len',
'rc_new_len' ] );
384 if ( in_array( self::INCLUDE_LOG_INFO,
$options[
'includeFields'] ) ) {
385 $fields = array_merge( $fields, [
'rc_logid',
'rc_log_type',
'rc_log_action',
'rc_params' ] );
397 $conds = [
'wl_user' => $watchlistOwnerId ];
401 [
'rc_this_oldid=page_latest',
'rc_type=' .
RC_LOG ],
407 $conds[
'wl_namespace'] = array_map(
'intval',
$options[
'namespaceIds'] );
410 if ( array_key_exists(
'rcTypes',
$options ) ) {
411 $conds[
'rc_type'] = array_map(
'intval',
$options[
'rcTypes'] );
414 $conds = array_merge(
422 if ( $db->
getType() ===
'mysql' ) {
424 $conds[] =
'rc_timestamp > ' . $db->
addQuotes(
'' );
431 if ( $deletedPageLogCond ) {
432 $conds[] = $deletedPageLogCond;
439 if ( array_key_exists(
'watchlistOwner',
$options ) ) {
441 $watchlistOwner =
$options[
'watchlistOwner'];
442 $ownersToken = $watchlistOwner->getOption(
'watchlisttoken' );
443 $token =
$options[
'watchlistOwnerToken'];
444 if ( $ownersToken ==
'' || !hash_equals( $ownersToken, $token ) ) {
447 return $watchlistOwner->getId();
449 return $user->getId();
455 if ( in_array( self::FILTER_MINOR,
$options[
'filters'] ) ) {
456 $conds[] =
'rc_minor != 0';
457 } elseif ( in_array( self::FILTER_NOT_MINOR,
$options[
'filters'] ) ) {
458 $conds[] =
'rc_minor = 0';
461 if ( in_array( self::FILTER_BOT,
$options[
'filters'] ) ) {
462 $conds[] =
'rc_bot != 0';
463 } elseif ( in_array( self::FILTER_NOT_BOT,
$options[
'filters'] ) ) {
464 $conds[] =
'rc_bot = 0';
467 if ( in_array( self::FILTER_ANON,
$options[
'filters'] ) ) {
468 $conds[] =
'rc_user = 0';
469 } elseif ( in_array( self::FILTER_NOT_ANON,
$options[
'filters'] ) ) {
470 $conds[] =
'rc_user != 0';
473 if (
$user->useRCPatrol() ||
$user->useNPPatrol() ) {
476 if ( in_array( self::FILTER_PATROLLED,
$options[
'filters'] ) ) {
477 $conds[] =
'rc_patrolled != 0';
478 } elseif ( in_array( self::FILTER_NOT_PATROLLED,
$options[
'filters'] ) ) {
479 $conds[] =
'rc_patrolled = 0';
483 if ( in_array( self::FILTER_UNREAD,
$options[
'filters'] ) ) {
484 $conds[] =
'rc_timestamp >= wl_notificationtimestamp';
485 } elseif ( in_array( self::FILTER_NOT_UNREAD,
$options[
'filters'] ) ) {
487 $conds[] =
'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp';
501 $after =
$options[
'dir'] === self::DIR_OLDER ?
'<=' :
'>=';
502 $conds[] =
'rc_timestamp ' . $after .
' ' .
506 $before =
$options[
'dir'] === self::DIR_OLDER ?
'>=' :
'<=';
507 $conds[] =
'rc_timestamp ' . $before .
' ' .
515 if ( !array_key_exists(
'onlyByUser',
$options ) && !array_key_exists(
'notByUser',
$options ) ) {
521 if ( array_key_exists(
'onlyByUser',
$options ) ) {
522 $conds[
'rc_user_text'] =
$options[
'onlyByUser'];
523 } elseif ( array_key_exists(
'notByUser',
$options ) ) {
529 if ( !
$user->isAllowed(
'deletedhistory' ) ) {
531 } elseif ( !
$user->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
535 $conds[] = $db->
bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask";
545 if ( !
$user->isAllowed(
'deletedhistory' ) ) {
547 } elseif ( !
$user->isAllowedAny(
'suppressrevision',
'viewsuppressed' ) ) {
553 $db->
bitAnd(
'rc_deleted', $bitmask ) .
" != $bitmask",
560 $op =
$options[
'dir'] === self::DIR_OLDER ?
'<' :
'>';
561 list( $rcTimestamp, $rcId ) = $startFrom;
566 "rc_timestamp $op $rcTimestamp",
569 "rc_timestamp = $rcTimestamp",
580 $conds = [
'wl_user' =>
$user->getId() ];
582 $conds[
'wl_namespace'] = array_map(
'intval',
$options[
'namespaceIds'] );
584 if ( isset(
$options[
'filter'] ) ) {
586 if ( $filter === self::FILTER_CHANGED ) {
587 $conds[] =
'wl_notificationtimestamp IS NOT NULL';
589 $conds[] =
'wl_notificationtimestamp IS NULL';
594 $op =
$options[
'sort'] === self::SORT_ASC ?
'>' :
'<';
598 $op =
$options[
'sort'] === self::SORT_ASC ?
'<' :
'>';
601 if ( isset(
$options[
'startFrom'] ) ) {
602 $op =
$options[
'sort'] === self::SORT_ASC ?
'>' :
'<';
637 if ( array_key_exists(
'dir',
$options ) ) {
639 $dbOptions[
'ORDER BY'] = [
'rc_timestamp' .
$sort,
'rc_id' .
$sort ];
642 if ( array_key_exists(
'limit',
$options ) ) {
643 $dbOptions[
'LIMIT'] = (int)
$options[
'limit'] + 1;
651 if ( array_key_exists(
'sort',
$options ) ) {
652 $dbOptions[
'ORDER BY'] = [
653 "wl_namespace {$options['sort']}",
654 "wl_title {$options['sort']}"
657 $dbOptions[
'ORDER BY'] =
"wl_title {$options['sort']}";
660 if ( array_key_exists(
'limit',
$options ) ) {
661 $dbOptions[
'LIMIT'] = (int)
$options[
'limit'];
668 'watchlist' => [
'INNER JOIN',
670 'wl_namespace=rc_namespace',
676 $joinConds[
'page'] = [
'LEFT JOIN',
'rc_cur_id=page_id' ];
678 if ( in_array( self::INCLUDE_COMMENT,
$options[
'includeFields'] ) ) {