MediaWiki  REL1_31
WatchedItemQueryService.php
Go to the documentation of this file.
1 <?php
2 
5 use Wikimedia\Assert\Assert;
7 
19 
20  const DIR_OLDER = 'older';
21  const DIR_NEWER = 'newer';
22 
23  const INCLUDE_FLAGS = 'flags';
24  const INCLUDE_USER = 'user';
25  const INCLUDE_USER_ID = 'userid';
26  const INCLUDE_COMMENT = 'comment';
27  const INCLUDE_PATROL_INFO = 'patrol';
28  const INCLUDE_AUTOPATROL_INFO = 'autopatrol';
29  const INCLUDE_SIZES = 'sizes';
30  const INCLUDE_LOG_INFO = 'loginfo';
31  const INCLUDE_TAGS = 'tags';
32 
33  // FILTER_* constants are part of public API (are used in ApiQueryWatchlist and
34  // ApiQueryWatchlistRaw classes) and should not be changed.
35  // Changing values of those constants will result in a breaking change in the API
36  const FILTER_MINOR = 'minor';
37  const FILTER_NOT_MINOR = '!minor';
38  const FILTER_BOT = 'bot';
39  const FILTER_NOT_BOT = '!bot';
40  const FILTER_ANON = 'anon';
41  const FILTER_NOT_ANON = '!anon';
42  const FILTER_PATROLLED = 'patrolled';
43  const FILTER_NOT_PATROLLED = '!patrolled';
44  const FILTER_AUTOPATROLLED = 'autopatrolled';
45  const FILTER_NOT_AUTOPATROLLED = '!autopatrolled';
46  const FILTER_UNREAD = 'unread';
47  const FILTER_NOT_UNREAD = '!unread';
48  const FILTER_CHANGED = 'changed';
49  const FILTER_NOT_CHANGED = '!changed';
50 
51  const SORT_ASC = 'ASC';
52  const SORT_DESC = 'DESC';
53 
57  private $loadBalancer;
58 
60  private $extensions = null;
61 
63  private $commentStore;
64 
66  private $actorMigration;
67 
68  public function __construct(
72  ) {
73  $this->loadBalancer = $loadBalancer;
74  $this->commentStore = $commentStore;
75  $this->actorMigration = $actorMigration;
76  }
77 
81  private function getExtensions() {
82  if ( $this->extensions === null ) {
83  $this->extensions = [];
84  Hooks::run( 'WatchedItemQueryServiceExtensions', [ &$this->extensions, $this ] );
85  }
86  return $this->extensions;
87  }
88 
93  private function getConnection() {
94  return $this->loadBalancer->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
95  }
96 
141  User $user, array $options = [], &$startFrom = null
142  ) {
143  $options += [
144  'includeFields' => [],
145  'namespaceIds' => [],
146  'filters' => [],
147  'allRevisions' => false,
148  'usedInGenerator' => false
149  ];
150 
151  Assert::parameter(
152  !isset( $options['rcTypes'] )
153  || !array_diff( $options['rcTypes'], [ RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL, RC_CATEGORIZE ] ),
154  '$options[\'rcTypes\']',
155  'must be an array containing only: RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL and/or RC_CATEGORIZE'
156  );
157  Assert::parameter(
158  !isset( $options['dir'] ) || in_array( $options['dir'], [ self::DIR_OLDER, self::DIR_NEWER ] ),
159  '$options[\'dir\']',
160  'must be DIR_OLDER or DIR_NEWER'
161  );
162  Assert::parameter(
163  !isset( $options['start'] ) && !isset( $options['end'] ) && $startFrom === null
164  || isset( $options['dir'] ),
165  '$options[\'dir\']',
166  'must be provided when providing the "start" or "end" options or the $startFrom parameter'
167  );
168  Assert::parameter(
169  !isset( $options['startFrom'] ),
170  '$options[\'startFrom\']',
171  'must not be provided, use $startFrom instead'
172  );
173  Assert::parameter(
174  !isset( $startFrom ) || ( is_array( $startFrom ) && count( $startFrom ) === 2 ),
175  '$startFrom',
176  'must be a two-element array'
177  );
178  if ( array_key_exists( 'watchlistOwner', $options ) ) {
179  Assert::parameterType(
180  User::class,
181  $options['watchlistOwner'],
182  '$options[\'watchlistOwner\']'
183  );
184  Assert::parameter(
185  isset( $options['watchlistOwnerToken'] ),
186  '$options[\'watchlistOwnerToken\']',
187  'must be provided when providing watchlistOwner option'
188  );
189  }
190 
191  $db = $this->getConnection();
192 
195  $conds = $this->getWatchedItemsWithRCInfoQueryConds( $db, $user, $options );
196  $dbOptions = $this->getWatchedItemsWithRCInfoQueryDbOptions( $options );
197  $joinConds = $this->getWatchedItemsWithRCInfoQueryJoinConds( $options );
198 
199  if ( $startFrom !== null ) {
200  $conds[] = $this->getStartFromConds( $db, $options, $startFrom );
201  }
202 
203  foreach ( $this->getExtensions() as $extension ) {
204  $extension->modifyWatchedItemsWithRCInfoQuery(
205  $user, $options, $db,
206  $tables,
207  $fields,
208  $conds,
209  $dbOptions,
210  $joinConds
211  );
212  }
213 
214  $res = $db->select(
215  $tables,
216  $fields,
217  $conds,
218  __METHOD__,
219  $dbOptions,
220  $joinConds
221  );
222 
223  $limit = isset( $dbOptions['LIMIT'] ) ? $dbOptions['LIMIT'] : INF;
224  $items = [];
225  $startFrom = null;
226  foreach ( $res as $row ) {
227  if ( --$limit <= 0 ) {
228  $startFrom = [ $row->rc_timestamp, $row->rc_id ];
229  break;
230  }
231 
232  $items[] = [
233  new WatchedItem(
234  $user,
235  new TitleValue( (int)$row->rc_namespace, $row->rc_title ),
236  $row->wl_notificationtimestamp
237  ),
238  $this->getRecentChangeFieldsFromRow( $row )
239  ];
240  }
241 
242  foreach ( $this->getExtensions() as $extension ) {
243  $extension->modifyWatchedItemsWithRCInfo( $user, $options, $db, $items, $res, $startFrom );
244  }
245 
246  return $items;
247  }
248 
268  public function getWatchedItemsForUser( User $user, array $options = [] ) {
269  if ( $user->isAnon() ) {
270  // TODO: should this just return an empty array or rather complain loud at this point
271  // as e.g. ApiBase::getWatchlistUser does?
272  return [];
273  }
274 
275  $options += [ 'namespaceIds' => [] ];
276 
277  Assert::parameter(
278  !isset( $options['sort'] ) || in_array( $options['sort'], [ self::SORT_ASC, self::SORT_DESC ] ),
279  '$options[\'sort\']',
280  'must be SORT_ASC or SORT_DESC'
281  );
282  Assert::parameter(
283  !isset( $options['filter'] ) || in_array(
284  $options['filter'], [ self::FILTER_CHANGED, self::FILTER_NOT_CHANGED ]
285  ),
286  '$options[\'filter\']',
287  'must be FILTER_CHANGED or FILTER_NOT_CHANGED'
288  );
289  Assert::parameter(
290  !isset( $options['from'] ) && !isset( $options['until'] ) && !isset( $options['startFrom'] )
291  || isset( $options['sort'] ),
292  '$options[\'sort\']',
293  'must be provided if any of "from", "until", "startFrom" options is provided'
294  );
295 
296  $db = $this->getConnection();
297 
298  $conds = $this->getWatchedItemsForUserQueryConds( $db, $user, $options );
299  $dbOptions = $this->getWatchedItemsForUserQueryDbOptions( $options );
300 
301  $res = $db->select(
302  'watchlist',
303  [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
304  $conds,
305  __METHOD__,
306  $dbOptions
307  );
308 
309  $watchedItems = [];
310  foreach ( $res as $row ) {
311  // todo these could all be cached at some point?
312  $watchedItems[] = new WatchedItem(
313  $user,
314  new TitleValue( (int)$row->wl_namespace, $row->wl_title ),
315  $row->wl_notificationtimestamp
316  );
317  }
318 
319  return $watchedItems;
320  }
321 
322  private function getRecentChangeFieldsFromRow( stdClass $row ) {
323  // FIXME: This can be simplified to single array_filter call filtering by key value,
324  // now we have stopped supporting PHP 5.5
325  $allFields = get_object_vars( $row );
326  $rcKeys = array_filter(
327  array_keys( $allFields ),
328  function ( $key ) {
329  return substr( $key, 0, 3 ) === 'rc_';
330  }
331  );
332  return array_intersect_key( $allFields, array_flip( $rcKeys ) );
333  }
334 
336  $tables = [ 'recentchanges', 'watchlist' ];
337  if ( !$options['allRevisions'] ) {
338  $tables[] = 'page';
339  }
340  if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
341  $tables += $this->commentStore->getJoin( 'rc_comment' )['tables'];
342  }
343  if ( in_array( self::INCLUDE_TAGS, $options['includeFields'] ) ) {
344  $tables[] = 'tag_summary';
345  }
346  if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ||
347  in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ||
348  in_array( self::FILTER_ANON, $options['filters'] ) ||
349  in_array( self::FILTER_NOT_ANON, $options['filters'] ) ||
350  array_key_exists( 'onlyByUser', $options ) || array_key_exists( 'notByUser', $options )
351  ) {
352  $tables += $this->actorMigration->getJoin( 'rc_user' )['tables'];
353  }
354  return $tables;
355  }
356 
358  $fields = [
359  'rc_id',
360  'rc_namespace',
361  'rc_title',
362  'rc_timestamp',
363  'rc_type',
364  'rc_deleted',
365  'wl_notificationtimestamp'
366  ];
367 
368  $rcIdFields = [
369  'rc_cur_id',
370  'rc_this_oldid',
371  'rc_last_oldid',
372  ];
373  if ( $options['usedInGenerator'] ) {
374  if ( $options['allRevisions'] ) {
375  $rcIdFields = [ 'rc_this_oldid' ];
376  } else {
377  $rcIdFields = [ 'rc_cur_id' ];
378  }
379  }
380  $fields = array_merge( $fields, $rcIdFields );
381 
382  if ( in_array( self::INCLUDE_FLAGS, $options['includeFields'] ) ) {
383  $fields = array_merge( $fields, [ 'rc_type', 'rc_minor', 'rc_bot' ] );
384  }
385  if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ) {
386  $fields['rc_user_text'] = $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user_text'];
387  }
388  if ( in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ) {
389  $fields['rc_user'] = $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user'];
390  }
391  if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
392  $fields += $this->commentStore->getJoin( 'rc_comment' )['fields'];
393  }
394  if ( in_array( self::INCLUDE_PATROL_INFO, $options['includeFields'] ) ) {
395  $fields = array_merge( $fields, [ 'rc_patrolled', 'rc_log_type' ] );
396  }
397  if ( in_array( self::INCLUDE_SIZES, $options['includeFields'] ) ) {
398  $fields = array_merge( $fields, [ 'rc_old_len', 'rc_new_len' ] );
399  }
400  if ( in_array( self::INCLUDE_LOG_INFO, $options['includeFields'] ) ) {
401  $fields = array_merge( $fields, [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ] );
402  }
403  if ( in_array( self::INCLUDE_TAGS, $options['includeFields'] ) ) {
404  // prefixed with rc_ to include the field in getRecentChangeFieldsFromRow
405  $fields['rc_tags'] = 'ts_tags';
406  }
407 
408  return $fields;
409  }
410 
412  IDatabase $db,
413  User $user,
415  ) {
416  $watchlistOwnerId = $this->getWatchlistOwnerId( $user, $options );
417  $conds = [ 'wl_user' => $watchlistOwnerId ];
418 
419  if ( !$options['allRevisions'] ) {
420  $conds[] = $db->makeList(
421  [ 'rc_this_oldid=page_latest', 'rc_type=' . RC_LOG ],
422  LIST_OR
423  );
424  }
425 
426  if ( $options['namespaceIds'] ) {
427  $conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
428  }
429 
430  if ( array_key_exists( 'rcTypes', $options ) ) {
431  $conds['rc_type'] = array_map( 'intval', $options['rcTypes'] );
432  }
433 
434  $conds = array_merge(
435  $conds,
437  );
438 
439  $conds = array_merge( $conds, $this->getStartEndConds( $db, $options ) );
440 
441  if ( !isset( $options['start'] ) && !isset( $options['end'] ) ) {
442  if ( $db->getType() === 'mysql' ) {
443  // This is an index optimization for mysql
444  $conds[] = 'rc_timestamp > ' . $db->addQuotes( '' );
445  }
446  }
447 
448  $conds = array_merge( $conds, $this->getUserRelatedConds( $db, $user, $options ) );
449 
450  $deletedPageLogCond = $this->getExtraDeletedPageLogEntryRelatedCond( $db, $user );
451  if ( $deletedPageLogCond ) {
452  $conds[] = $deletedPageLogCond;
453  }
454 
455  return $conds;
456  }
457 
458  private function getWatchlistOwnerId( User $user, array $options ) {
459  if ( array_key_exists( 'watchlistOwner', $options ) ) {
461  $watchlistOwner = $options['watchlistOwner'];
462  $ownersToken = $watchlistOwner->getOption( 'watchlisttoken' );
463  $token = $options['watchlistOwnerToken'];
464  if ( $ownersToken == '' || !hash_equals( $ownersToken, $token ) ) {
465  throw ApiUsageException::newWithMessage( null, 'apierror-bad-watchlist-token', 'bad_wltoken' );
466  }
467  return $watchlistOwner->getId();
468  }
469  return $user->getId();
470  }
471 
473  $conds = [];
474 
475  if ( in_array( self::FILTER_MINOR, $options['filters'] ) ) {
476  $conds[] = 'rc_minor != 0';
477  } elseif ( in_array( self::FILTER_NOT_MINOR, $options['filters'] ) ) {
478  $conds[] = 'rc_minor = 0';
479  }
480 
481  if ( in_array( self::FILTER_BOT, $options['filters'] ) ) {
482  $conds[] = 'rc_bot != 0';
483  } elseif ( in_array( self::FILTER_NOT_BOT, $options['filters'] ) ) {
484  $conds[] = 'rc_bot = 0';
485  }
486 
487  if ( in_array( self::FILTER_ANON, $options['filters'] ) ) {
488  $conds[] = $this->actorMigration->isAnon(
489  $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user']
490  );
491  } elseif ( in_array( self::FILTER_NOT_ANON, $options['filters'] ) ) {
492  $conds[] = $this->actorMigration->isNotAnon(
493  $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user']
494  );
495  }
496 
497  if ( $user->useRCPatrol() || $user->useNPPatrol() ) {
498  // TODO: not sure if this should simply ignore patrolled filters if user does not have the patrol
499  // right, or maybe rather fail loud at this point, same as e.g. ApiQueryWatchlist does?
500  if ( in_array( self::FILTER_PATROLLED, $options['filters'] ) ) {
501  $conds[] = 'rc_patrolled != ' . RecentChange::PRC_UNPATROLLED;
502  } elseif ( in_array( self::FILTER_NOT_PATROLLED, $options['filters'] ) ) {
503  $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
504  }
505 
506  if ( in_array( self::FILTER_AUTOPATROLLED, $options['filters'] ) ) {
507  $conds['rc_patrolled'] = RecentChange::PRC_AUTOPATROLLED;
508  } elseif ( in_array( self::FILTER_NOT_AUTOPATROLLED, $options['filters'] ) ) {
509  $conds[] = 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED;
510  }
511  }
512 
513  if ( in_array( self::FILTER_UNREAD, $options['filters'] ) ) {
514  $conds[] = 'rc_timestamp >= wl_notificationtimestamp';
515  } elseif ( in_array( self::FILTER_NOT_UNREAD, $options['filters'] ) ) {
516  // TODO: should this be changed to use Database::makeList?
517  $conds[] = 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp';
518  }
519 
520  return $conds;
521  }
522 
523  private function getStartEndConds( IDatabase $db, array $options ) {
524  if ( !isset( $options['start'] ) && !isset( $options['end'] ) ) {
525  return [];
526  }
527 
528  $conds = [];
529 
530  if ( isset( $options['start'] ) ) {
531  $after = $options['dir'] === self::DIR_OLDER ? '<=' : '>=';
532  $conds[] = 'rc_timestamp ' . $after . ' ' .
533  $db->addQuotes( $db->timestamp( $options['start'] ) );
534  }
535  if ( isset( $options['end'] ) ) {
536  $before = $options['dir'] === self::DIR_OLDER ? '>=' : '<=';
537  $conds[] = 'rc_timestamp ' . $before . ' ' .
538  $db->addQuotes( $db->timestamp( $options['end'] ) );
539  }
540 
541  return $conds;
542  }
543 
544  private function getUserRelatedConds( IDatabase $db, User $user, array $options ) {
545  if ( !array_key_exists( 'onlyByUser', $options ) && !array_key_exists( 'notByUser', $options ) ) {
546  return [];
547  }
548 
549  $conds = [];
550 
551  if ( array_key_exists( 'onlyByUser', $options ) ) {
552  $byUser = User::newFromName( $options['onlyByUser'], false );
553  $conds[] = $this->actorMigration->getWhere( $db, 'rc_user', $byUser )['conds'];
554  } elseif ( array_key_exists( 'notByUser', $options ) ) {
555  $byUser = User::newFromName( $options['notByUser'], false );
556  $conds[] = 'NOT(' . $this->actorMigration->getWhere( $db, 'rc_user', $byUser )['conds'] . ')';
557  }
558 
559  // Avoid brute force searches (T19342)
560  $bitmask = 0;
561  if ( !$user->isAllowed( 'deletedhistory' ) ) {
562  $bitmask = Revision::DELETED_USER;
563  } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
565  }
566  if ( $bitmask ) {
567  $conds[] = $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask";
568  }
569 
570  return $conds;
571  }
572 
574  // LogPage::DELETED_ACTION hides the affected page, too. So hide those
575  // entirely from the watchlist, or someone could guess the title.
576  $bitmask = 0;
577  if ( !$user->isAllowed( 'deletedhistory' ) ) {
578  $bitmask = LogPage::DELETED_ACTION;
579  } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
581  }
582  if ( $bitmask ) {
583  return $db->makeList( [
584  'rc_type != ' . RC_LOG,
585  $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
586  ], LIST_OR );
587  }
588  return '';
589  }
590 
591  private function getStartFromConds( IDatabase $db, array $options, array $startFrom ) {
592  $op = $options['dir'] === self::DIR_OLDER ? '<' : '>';
593  list( $rcTimestamp, $rcId ) = $startFrom;
594  $rcTimestamp = $db->addQuotes( $db->timestamp( $rcTimestamp ) );
595  $rcId = (int)$rcId;
596  return $db->makeList(
597  [
598  "rc_timestamp $op $rcTimestamp",
599  $db->makeList(
600  [
601  "rc_timestamp = $rcTimestamp",
602  "rc_id $op= $rcId"
603  ],
604  LIST_AND
605  )
606  ],
607  LIST_OR
608  );
609  }
610 
612  $conds = [ 'wl_user' => $user->getId() ];
613  if ( $options['namespaceIds'] ) {
614  $conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
615  }
616  if ( isset( $options['filter'] ) ) {
617  $filter = $options['filter'];
618  if ( $filter === self::FILTER_CHANGED ) {
619  $conds[] = 'wl_notificationtimestamp IS NOT NULL';
620  } else {
621  $conds[] = 'wl_notificationtimestamp IS NULL';
622  }
623  }
624 
625  if ( isset( $options['from'] ) ) {
626  $op = $options['sort'] === self::SORT_ASC ? '>' : '<';
627  $conds[] = $this->getFromUntilTargetConds( $db, $options['from'], $op );
628  }
629  if ( isset( $options['until'] ) ) {
630  $op = $options['sort'] === self::SORT_ASC ? '<' : '>';
631  $conds[] = $this->getFromUntilTargetConds( $db, $options['until'], $op );
632  }
633  if ( isset( $options['startFrom'] ) ) {
634  $op = $options['sort'] === self::SORT_ASC ? '>' : '<';
635  $conds[] = $this->getFromUntilTargetConds( $db, $options['startFrom'], $op );
636  }
637 
638  return $conds;
639  }
640 
650  private function getFromUntilTargetConds( IDatabase $db, LinkTarget $target, $op ) {
651  return $db->makeList(
652  [
653  "wl_namespace $op " . $target->getNamespace(),
654  $db->makeList(
655  [
656  'wl_namespace = ' . $target->getNamespace(),
657  "wl_title $op= " . $db->addQuotes( $target->getDBkey() )
658  ],
659  LIST_AND
660  )
661  ],
662  LIST_OR
663  );
664  }
665 
667  $dbOptions = [];
668 
669  if ( array_key_exists( 'dir', $options ) ) {
670  $sort = $options['dir'] === self::DIR_OLDER ? ' DESC' : '';
671  $dbOptions['ORDER BY'] = [ 'rc_timestamp' . $sort, 'rc_id' . $sort ];
672  }
673 
674  if ( array_key_exists( 'limit', $options ) ) {
675  $dbOptions['LIMIT'] = (int)$options['limit'] + 1;
676  }
677 
678  return $dbOptions;
679  }
680 
682  $dbOptions = [];
683  if ( array_key_exists( 'sort', $options ) ) {
684  $dbOptions['ORDER BY'] = [
685  "wl_namespace {$options['sort']}",
686  "wl_title {$options['sort']}"
687  ];
688  if ( count( $options['namespaceIds'] ) === 1 ) {
689  $dbOptions['ORDER BY'] = "wl_title {$options['sort']}";
690  }
691  }
692  if ( array_key_exists( 'limit', $options ) ) {
693  $dbOptions['LIMIT'] = (int)$options['limit'];
694  }
695  return $dbOptions;
696  }
697 
699  $joinConds = [
700  'watchlist' => [ 'INNER JOIN',
701  [
702  'wl_namespace=rc_namespace',
703  'wl_title=rc_title'
704  ]
705  ]
706  ];
707  if ( !$options['allRevisions'] ) {
708  $joinConds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
709  }
710  if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
711  $joinConds += $this->commentStore->getJoin( 'rc_comment' )['joins'];
712  }
713  if ( in_array( self::INCLUDE_TAGS, $options['includeFields'] ) ) {
714  $joinConds['tag_summary'] = [ 'LEFT JOIN', [ 'rc_id=ts_rc_id' ] ];
715  }
716  if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ||
717  in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ||
718  in_array( self::FILTER_ANON, $options['filters'] ) ||
719  in_array( self::FILTER_NOT_ANON, $options['filters'] ) ||
720  array_key_exists( 'onlyByUser', $options ) || array_key_exists( 'notByUser', $options )
721  ) {
722  $joinConds += $this->actorMigration->getJoin( 'rc_user' )['joins'];
723  }
724  return $joinConds;
725  }
726 
727 }
WatchedItemQueryService\getConnection
getConnection()
Definition: WatchedItemQueryService.php:93
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:247
Revision\DELETED_USER
const DELETED_USER
Definition: Revision.php:49
Revision\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: Revision.php:50
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:650
RC_EXTERNAL
const RC_EXTERNAL
Definition: Defines.php:155
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryConds
getWatchedItemsWithRCInfoQueryConds(IDatabase $db, User $user, array $options)
Definition: WatchedItemQueryService.php:411
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryJoinConds
getWatchedItemsWithRCInfoQueryJoinConds(array $options)
Definition: WatchedItemQueryService.php:698
WatchedItemQueryServiceExtension
Definition: WatchedItemQueryServiceExtension.php:16
WatchedItemQueryService\getUserRelatedConds
getUserRelatedConds(IDatabase $db, User $user, array $options)
Definition: WatchedItemQueryService.php:544
WatchedItemQueryService\$commentStore
CommentStore $commentStore
Definition: WatchedItemQueryService.php:63
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
WatchedItemQueryService\INCLUDE_COMMENT
const INCLUDE_COMMENT
Definition: WatchedItemQueryService.php:26
WatchedItemQueryService\$loadBalancer
LoadBalancer $loadBalancer
Definition: WatchedItemQueryService.php:57
WatchedItemQueryService\getWatchedItemsForUserQueryConds
getWatchedItemsForUserQueryConds(IDatabase $db, User $user, array $options)
Definition: WatchedItemQueryService.php:611
Wikimedia\Rdbms\IDatabase\makeList
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
array
the array() calling protocol came about after MediaWiki 1.4rc1.
WatchedItemQueryService\FILTER_NOT_AUTOPATROLLED
const FILTER_NOT_AUTOPATROLLED
Definition: WatchedItemQueryService.php:45
WatchedItemQueryService\FILTER_PATROLLED
const FILTER_PATROLLED
Definition: WatchedItemQueryService.php:42
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryFields
getWatchedItemsWithRCInfoQueryFields(array $options)
Definition: WatchedItemQueryService.php:357
WatchedItemQueryService\getStartEndConds
getStartEndConds(IDatabase $db, array $options)
Definition: WatchedItemQueryService.php:523
RC_LOG
const RC_LOG
Definition: Defines.php:154
WatchedItemQueryService\getExtraDeletedPageLogEntryRelatedCond
getExtraDeletedPageLogEntryRelatedCond(IDatabase $db, User $user)
Definition: WatchedItemQueryService.php:573
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryDbOptions
getWatchedItemsWithRCInfoQueryDbOptions(array $options)
Definition: WatchedItemQueryService.php:666
CommentStore
CommentStore handles storage of comments (edit summaries, log reasons, etc) in the database.
Definition: CommentStore.php:31
WatchedItemQueryService\__construct
__construct(LoadBalancer $loadBalancer, CommentStore $commentStore, ActorMigration $actorMigration)
Definition: WatchedItemQueryService.php:68
RC_EDIT
const RC_EDIT
Definition: Defines.php:152
WatchedItemQueryService\getWatchedItemsForUserQueryDbOptions
getWatchedItemsForUserQueryDbOptions(array $options)
Definition: WatchedItemQueryService.php:681
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:591
$res
$res
Definition: database.txt:21
WatchedItemQueryService\getWatchedItemsWithRecentChangeInfo
getWatchedItemsWithRecentChangeInfo(User $user, array $options=[], &$startFrom=null)
Definition: WatchedItemQueryService.php:140
ActorMigration
This class handles the logic for the actor table migration.
Definition: ActorMigration.php:35
WatchedItemQueryService\$actorMigration
ActorMigration $actorMigration
Definition: WatchedItemQueryService.php:66
ApiUsageException\newWithMessage
static newWithMessage(ApiBase $module=null, $msg, $code=null, $data=null, $httpCode=0)
Definition: ApiUsageException.php:143
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:37
LIST_AND
const LIST_AND
Definition: Defines.php:53
WatchedItemQueryService\INCLUDE_TAGS
const INCLUDE_TAGS
Definition: WatchedItemQueryService.php:31
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
WatchedItemQueryService\INCLUDE_LOG_INFO
const INCLUDE_LOG_INFO
Definition: WatchedItemQueryService.php:30
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 wfTimestamp() to the format used for inserting ...
LIST_OR
const LIST_OR
Definition: Defines.php:56
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryFilterConds
getWatchedItemsWithRCInfoQueryFilterConds(User $user, array $options)
Definition: WatchedItemQueryService.php:472
WatchedItemQueryService\FILTER_MINOR
const FILTER_MINOR
Definition: WatchedItemQueryService.php:36
WatchedItemQueryService\FILTER_NOT_CHANGED
const FILTER_NOT_CHANGED
Definition: WatchedItemQueryService.php:49
WatchedItemQueryService\INCLUDE_AUTOPATROL_INFO
const INCLUDE_AUTOPATROL_INFO
Definition: WatchedItemQueryService.php:28
WatchedItemQueryService\INCLUDE_USER
const INCLUDE_USER
Definition: WatchedItemQueryService.php:24
WatchedItemQueryService\FILTER_NOT_BOT
const FILTER_NOT_BOT
Definition: WatchedItemQueryService.php:39
WatchedItemQueryService
Definition: WatchedItemQueryService.php:18
WatchedItemQueryService\INCLUDE_PATROL_INFO
const INCLUDE_PATROL_INFO
Definition: WatchedItemQueryService.php:27
WatchedItemQueryService\FILTER_BOT
const FILTER_BOT
Definition: WatchedItemQueryService.php:38
WatchedItemQueryService\INCLUDE_USER_ID
const INCLUDE_USER_ID
Definition: WatchedItemQueryService.php:25
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
$sort
$sort
Definition: profileinfo.php:317
LogPage\DELETED_ACTION
const DELETED_ACTION
Definition: LogPage.php:32
Wikimedia\Rdbms\LoadBalancer
Database connection, tracking, load balancing, and transaction manager for a cluster.
Definition: LoadBalancer.php:41
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:2001
WatchedItemQueryService\DIR_NEWER
const DIR_NEWER
Definition: WatchedItemQueryService.php:21
WatchedItemQueryService\SORT_DESC
const SORT_DESC
Definition: WatchedItemQueryService.php:52
extensions
The ContentHandler facility adds support for arbitrary content types on wiki instead of relying on wikitext for everything It was introduced in MediaWiki Each kind of and so on Built in content types as usual *javascript user provided javascript code *json simple implementation for use by extensions
Definition: contenthandler.txt:11
WatchedItemQueryService\getRecentChangeFieldsFromRow
getRecentChangeFieldsFromRow(stdClass $row)
Definition: WatchedItemQueryService.php:322
WatchedItem
Representation of a pair of user and title for watchlist entries.
Definition: WatchedItem.php:32
WatchedItemQueryService\FILTER_NOT_PATROLLED
const FILTER_NOT_PATROLLED
Definition: WatchedItemQueryService.php:43
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
WatchedItemQueryService\SORT_ASC
const SORT_ASC
Definition: WatchedItemQueryService.php:51
WatchedItemQueryService\FILTER_NOT_MINOR
const FILTER_NOT_MINOR
Definition: WatchedItemQueryService.php:37
RC_NEW
const RC_NEW
Definition: Defines.php:153
WatchedItemQueryService\INCLUDE_FLAGS
const INCLUDE_FLAGS
Definition: WatchedItemQueryService.php:23
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:79
Wikimedia\Rdbms\IDatabase\bitAnd
bitAnd( $fieldLeft, $fieldRight)
WatchedItemQueryService\DIR_OLDER
const DIR_OLDER
Definition: WatchedItemQueryService.php:20
WatchedItemQueryService\getExtensions
getExtensions()
Definition: WatchedItemQueryService.php:81
Wikimedia\Rdbms\IDatabase\addQuotes
addQuotes( $s)
Adds quotes and backslashes.
WatchedItemQueryService\FILTER_CHANGED
const FILTER_CHANGED
Definition: WatchedItemQueryService.php:48
WatchedItemQueryService\FILTER_AUTOPATROLLED
const FILTER_AUTOPATROLLED
Definition: WatchedItemQueryService.php:44
WatchedItemQueryService\FILTER_NOT_UNREAD
const FILTER_NOT_UNREAD
Definition: WatchedItemQueryService.php:47
$tables
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition: hooks.txt:1015
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:77
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:22
WatchedItemQueryService\getWatchlistOwnerId
getWatchlistOwnerId(User $user, array $options)
Definition: WatchedItemQueryService.php:458
WatchedItemQueryService\FILTER_NOT_ANON
const FILTER_NOT_ANON
Definition: WatchedItemQueryService.php:41
Wikimedia\Rdbms\IDatabase\getType
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
WatchedItemQueryService\getWatchedItemsWithRCInfoQueryTables
getWatchedItemsWithRCInfoQueryTables(array $options)
Definition: WatchedItemQueryService.php:335
LogPage\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: LogPage.php:35
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:56
RC_CATEGORIZE
const RC_CATEGORIZE
Definition: Defines.php:156
WatchedItemQueryService\INCLUDE_SIZES
const INCLUDE_SIZES
Definition: WatchedItemQueryService.php:29
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
WatchedItemQueryService\getWatchedItemsForUser
getWatchedItemsForUser(User $user, array $options=[])
For simple listing of user's watchlist items, see WatchedItemStore::getWatchedItemsForUser.
Definition: WatchedItemQueryService.php:268
WatchedItemQueryService\FILTER_ANON
const FILTER_ANON
Definition: WatchedItemQueryService.php:40
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
WatchedItemQueryService\FILTER_UNREAD
const FILTER_UNREAD
Definition: WatchedItemQueryService.php:46
WatchedItemQueryService\$extensions
WatchedItemQueryServiceExtension[] null $extensions
Definition: WatchedItemQueryService.php:60
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
TitleValue
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:35
WatchedItemQueryService\getStartFromConds
getStartFromConds(IDatabase $db, array $options, array $startFrom)
Definition: WatchedItemQueryService.php:591