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