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