MediaWiki REL1_33
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
58
60 private $extensions = null;
61
64
67
70
71 public function __construct(
72 LoadBalancer $loadBalancer,
73 CommentStore $commentStore,
74 ActorMigration $actorMigration,
75 WatchedItemStoreInterface $watchedItemStore
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'] )
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 );
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,
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 ],
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 ],
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}
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
This class handles the logic for the actor table migration.
static newWithMessage(ApiBase $module=null, $msg, $code=null, $data=null, $httpCode=0)
static makeTagSummarySubquery( $tables)
Make the tag summary subquery based on the given tables and return it.
CommentStore handles storage of comments (edit summaries, log reasons, etc) in the database.
const DELETED_RESTRICTED
Definition LogPage.php:37
const DELETED_ACTION
Definition LogPage.php:34
const DELETED_USER
Definition Revision.php:48
const DELETED_RESTRICTED
Definition Revision.php:49
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:48
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:585
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)
WatchedItemStoreInterface $watchedItemStore
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...
__construct(LoadBalancer $loadBalancer, CommentStore $commentStore, ActorMigration $actorMigration, WatchedItemStoreInterface $watchedItemStore)
getStartEndConds(IDatabase $db, array $options)
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
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:1999
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:996
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
const RC_NEW
Definition Defines.php:152
const LIST_OR
Definition Defines.php:55
const RC_LOG
Definition Defines.php:153
const RC_EXTERNAL
Definition Defines.php:154
const LIST_AND
Definition Defines.php:52
const RC_EDIT
Definition Defines.php:151
const RC_CATEGORIZE
Definition Defines.php:155
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.
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))
$sort
$filter
const DB_REPLICA
Definition defines.php:25