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