MediaWiki REL1_31
WatchedItemQueryService.php
Go to the documentation of this file.
1<?php
2
5use 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
58
60 private $extensions = null;
61
64
67
68 public function __construct(
69 LoadBalancer $loadBalancer,
70 CommentStore $commentStore,
71 ActorMigration $actorMigration
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 );
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,
414 array $options
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
472 private function getWatchedItemsWithRCInfoQueryFilterConds( User $user, array $options ) {
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' ) ) {
564 $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
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 ],
605 )
606 ],
607 LIST_OR
608 );
609 }
610
611 private function getWatchedItemsForUserQueryConds( IDatabase $db, User $user, array $options ) {
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 ],
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}
This class handles the logic for the actor table migration.
static newWithMessage(ApiBase $module=null, $msg, $code=null, $data=null, $httpCode=0)
CommentStore handles storage of comments (edit summaries, log reasons, etc) in the database.
const DELETED_RESTRICTED
Definition LogPage.php:35
const DELETED_ACTION
Definition LogPage.php:32
Represents a page (or page fragment) title within MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:53
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:591
getWatchedItemsForUser(User $user, array $options=[])
For simple listing of user's watchlist items, see WatchedItemStore::getWatchedItemsForUser.
getExtraDeletedPageLogEntryRelatedCond(IDatabase $db, User $user)
WatchedItemQueryServiceExtension[] null $extensions
getUserRelatedConds(IDatabase $db, User $user, array $options)
getWatchedItemsWithRCInfoQueryDbOptions(array $options)
getWatchedItemsWithRCInfoQueryFilterConds(User $user, array $options)
getWatchedItemsWithRCInfoQueryTables(array $options)
getWatchedItemsWithRCInfoQueryFields(array $options)
getWatchedItemsWithRCInfoQueryConds(IDatabase $db, User $user, array $options)
getWatchedItemsForUserQueryConds(IDatabase $db, User $user, array $options)
getFromUntilTargetConds(IDatabase $db, LinkTarget $target, $op)
Creates a query condition part for getting only items before or after the given link target (while or...
getStartEndConds(IDatabase $db, array $options)
__construct(LoadBalancer $loadBalancer, CommentStore $commentStore, ActorMigration $actorMigration)
getWatchlistOwnerId(User $user, array $options)
getWatchedItemsWithRecentChangeInfo(User $user, array $options=[], &$startFrom=null)
getWatchedItemsWithRCInfoQueryJoinConds(array $options)
getWatchedItemsForUserQueryDbOptions(array $options)
getStartFromConds(IDatabase $db, array $options, array $startFrom)
Representation of a pair of user and title for watchlist entries.
Database connection, tracking, load balancing, and transaction manager for a cluster.
$res
Definition database.txt:21
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
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
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
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
const RC_NEW
Definition Defines.php:153
const LIST_OR
Definition Defines.php:56
const RC_LOG
Definition Defines.php:154
const RC_EXTERNAL
Definition Defines.php:155
const LIST_AND
Definition Defines.php:53
const RC_EDIT
Definition Defines.php:152
const RC_CATEGORIZE
Definition Defines.php:156
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
bitAnd( $fieldLeft, $fieldRight)
addQuotes( $s)
Adds quotes and backslashes.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
$sort
const DB_REPLICA
Definition defines.php:25