MediaWiki REL1_29
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_SIZES = 'sizes';
29 const INCLUDE_LOG_INFO = 'loginfo';
30
31 // FILTER_* constants are part of public API (are used in ApiQueryWatchlist and
32 // ApiQueryWatchlistRaw classes) and should not be changed.
33 // Changing values of those constants will result in a breaking change in the API
34 const FILTER_MINOR = 'minor';
35 const FILTER_NOT_MINOR = '!minor';
36 const FILTER_BOT = 'bot';
37 const FILTER_NOT_BOT = '!bot';
38 const FILTER_ANON = 'anon';
39 const FILTER_NOT_ANON = '!anon';
40 const FILTER_PATROLLED = 'patrolled';
41 const FILTER_NOT_PATROLLED = '!patrolled';
42 const FILTER_UNREAD = 'unread';
43 const FILTER_NOT_UNREAD = '!unread';
44 const FILTER_CHANGED = 'changed';
45 const FILTER_NOT_CHANGED = '!changed';
46
47 const SORT_ASC = 'ASC';
48 const SORT_DESC = 'DESC';
49
54
56 private $extensions = null;
57
58 public function __construct( LoadBalancer $loadBalancer ) {
59 $this->loadBalancer = $loadBalancer;
60 }
61
65 private function getExtensions() {
66 if ( $this->extensions === null ) {
67 $this->extensions = [];
68 Hooks::run( 'WatchedItemQueryServiceExtensions', [ &$this->extensions, $this ] );
69 }
70 return $this->extensions;
71 }
72
77 private function getConnection() {
78 return $this->loadBalancer->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
79 }
80
125 User $user, array $options = [], &$startFrom = null
126 ) {
127 $options += [
128 'includeFields' => [],
129 'namespaceIds' => [],
130 'filters' => [],
131 'allRevisions' => false,
132 'usedInGenerator' => false
133 ];
134
135 Assert::parameter(
136 !isset( $options['rcTypes'] )
137 || !array_diff( $options['rcTypes'], [ RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL, RC_CATEGORIZE ] ),
138 '$options[\'rcTypes\']',
139 'must be an array containing only: RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL and/or RC_CATEGORIZE'
140 );
141 Assert::parameter(
142 !isset( $options['dir'] ) || in_array( $options['dir'], [ self::DIR_OLDER, self::DIR_NEWER ] ),
143 '$options[\'dir\']',
144 'must be DIR_OLDER or DIR_NEWER'
145 );
146 Assert::parameter(
147 !isset( $options['start'] ) && !isset( $options['end'] ) && $startFrom === null
148 || isset( $options['dir'] ),
149 '$options[\'dir\']',
150 'must be provided when providing the "start" or "end" options or the $startFrom parameter'
151 );
152 Assert::parameter(
153 !isset( $options['startFrom'] ),
154 '$options[\'startFrom\']',
155 'must not be provided, use $startFrom instead'
156 );
157 Assert::parameter(
158 !isset( $startFrom ) || ( is_array( $startFrom ) && count( $startFrom ) === 2 ),
159 '$startFrom',
160 'must be a two-element array'
161 );
162 if ( array_key_exists( 'watchlistOwner', $options ) ) {
163 Assert::parameterType(
164 User::class,
165 $options['watchlistOwner'],
166 '$options[\'watchlistOwner\']'
167 );
168 Assert::parameter(
169 isset( $options['watchlistOwnerToken'] ),
170 '$options[\'watchlistOwnerToken\']',
171 'must be provided when providing watchlistOwner option'
172 );
173 }
174
175 $tables = [ 'recentchanges', 'watchlist' ];
176 if ( !$options['allRevisions'] ) {
177 $tables[] = 'page';
178 }
179
180 $db = $this->getConnection();
181
183 $conds = $this->getWatchedItemsWithRCInfoQueryConds( $db, $user, $options );
186
187 if ( $startFrom !== null ) {
188 $conds[] = $this->getStartFromConds( $db, $options, $startFrom );
189 }
190
191 foreach ( $this->getExtensions() as $extension ) {
192 $extension->modifyWatchedItemsWithRCInfoQuery(
193 $user, $options, $db,
194 $tables,
195 $fields,
196 $conds,
197 $dbOptions,
198 $joinConds
199 );
200 }
201
202 $res = $db->select(
203 $tables,
204 $fields,
205 $conds,
206 __METHOD__,
207 $dbOptions,
208 $joinConds
209 );
210
211 $limit = isset( $dbOptions['LIMIT'] ) ? $dbOptions['LIMIT'] : INF;
212 $items = [];
213 $startFrom = null;
214 foreach ( $res as $row ) {
215 if ( --$limit <= 0 ) {
216 $startFrom = [ $row->rc_timestamp, $row->rc_id ];
217 break;
218 }
219
220 $items[] = [
221 new WatchedItem(
222 $user,
223 new TitleValue( (int)$row->rc_namespace, $row->rc_title ),
224 $row->wl_notificationtimestamp
225 ),
226 $this->getRecentChangeFieldsFromRow( $row )
227 ];
228 }
229
230 foreach ( $this->getExtensions() as $extension ) {
231 $extension->modifyWatchedItemsWithRCInfo( $user, $options, $db, $items, $res, $startFrom );
232 }
233
234 return $items;
235 }
236
256 public function getWatchedItemsForUser( User $user, array $options = [] ) {
257 if ( $user->isAnon() ) {
258 // TODO: should this just return an empty array or rather complain loud at this point
259 // as e.g. ApiBase::getWatchlistUser does?
260 return [];
261 }
262
263 $options += [ 'namespaceIds' => [] ];
264
265 Assert::parameter(
266 !isset( $options['sort'] ) || in_array( $options['sort'], [ self::SORT_ASC, self::SORT_DESC ] ),
267 '$options[\'sort\']',
268 'must be SORT_ASC or SORT_DESC'
269 );
270 Assert::parameter(
271 !isset( $options['filter'] ) || in_array(
272 $options['filter'], [ self::FILTER_CHANGED, self::FILTER_NOT_CHANGED ]
273 ),
274 '$options[\'filter\']',
275 'must be FILTER_CHANGED or FILTER_NOT_CHANGED'
276 );
277 Assert::parameter(
278 !isset( $options['from'] ) && !isset( $options['until'] ) && !isset( $options['startFrom'] )
279 || isset( $options['sort'] ),
280 '$options[\'sort\']',
281 'must be provided if any of "from", "until", "startFrom" options is provided'
282 );
283
284 $db = $this->getConnection();
285
286 $conds = $this->getWatchedItemsForUserQueryConds( $db, $user, $options );
287 $dbOptions = $this->getWatchedItemsForUserQueryDbOptions( $options );
288
289 $res = $db->select(
290 'watchlist',
291 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
292 $conds,
293 __METHOD__,
294 $dbOptions
295 );
296
297 $watchedItems = [];
298 foreach ( $res as $row ) {
299 // todo these could all be cached at some point?
300 $watchedItems[] = new WatchedItem(
301 $user,
302 new TitleValue( (int)$row->wl_namespace, $row->wl_title ),
303 $row->wl_notificationtimestamp
304 );
305 }
306
307 return $watchedItems;
308 }
309
310 private function getRecentChangeFieldsFromRow( stdClass $row ) {
311 // This can be simplified to single array_filter call filtering by key value,
312 // once we stop supporting PHP 5.5
313 $allFields = get_object_vars( $row );
314 $rcKeys = array_filter(
315 array_keys( $allFields ),
316 function( $key ) {
317 return substr( $key, 0, 3 ) === 'rc_';
318 }
319 );
320 return array_intersect_key( $allFields, array_flip( $rcKeys ) );
321 }
322
324 $fields = [
325 'rc_id',
326 'rc_namespace',
327 'rc_title',
328 'rc_timestamp',
329 'rc_type',
330 'rc_deleted',
331 'wl_notificationtimestamp'
332 ];
333
334 $rcIdFields = [
335 'rc_cur_id',
336 'rc_this_oldid',
337 'rc_last_oldid',
338 ];
339 if ( $options['usedInGenerator'] ) {
340 if ( $options['allRevisions'] ) {
341 $rcIdFields = [ 'rc_this_oldid' ];
342 } else {
343 $rcIdFields = [ 'rc_cur_id' ];
344 }
345 }
346 $fields = array_merge( $fields, $rcIdFields );
347
348 if ( in_array( self::INCLUDE_FLAGS, $options['includeFields'] ) ) {
349 $fields = array_merge( $fields, [ 'rc_type', 'rc_minor', 'rc_bot' ] );
350 }
351 if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ) {
352 $fields[] = 'rc_user_text';
353 }
354 if ( in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ) {
355 $fields[] = 'rc_user';
356 }
357 if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
358 $fields[] = 'rc_comment';
359 }
360 if ( in_array( self::INCLUDE_PATROL_INFO, $options['includeFields'] ) ) {
361 $fields = array_merge( $fields, [ 'rc_patrolled', 'rc_log_type' ] );
362 }
363 if ( in_array( self::INCLUDE_SIZES, $options['includeFields'] ) ) {
364 $fields = array_merge( $fields, [ 'rc_old_len', 'rc_new_len' ] );
365 }
366 if ( in_array( self::INCLUDE_LOG_INFO, $options['includeFields'] ) ) {
367 $fields = array_merge( $fields, [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ] );
368 }
369
370 return $fields;
371 }
372
374 IDatabase $db,
375 User $user,
377 ) {
378 $watchlistOwnerId = $this->getWatchlistOwnerId( $user, $options );
379 $conds = [ 'wl_user' => $watchlistOwnerId ];
380
381 if ( !$options['allRevisions'] ) {
382 $conds[] = $db->makeList(
383 [ 'rc_this_oldid=page_latest', 'rc_type=' . RC_LOG ],
384 LIST_OR
385 );
386 }
387
388 if ( $options['namespaceIds'] ) {
389 $conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
390 }
391
392 if ( array_key_exists( 'rcTypes', $options ) ) {
393 $conds['rc_type'] = array_map( 'intval', $options['rcTypes'] );
394 }
395
396 $conds = array_merge(
397 $conds,
399 );
400
401 $conds = array_merge( $conds, $this->getStartEndConds( $db, $options ) );
402
403 if ( !isset( $options['start'] ) && !isset( $options['end'] ) ) {
404 if ( $db->getType() === 'mysql' ) {
405 // This is an index optimization for mysql
406 $conds[] = 'rc_timestamp > ' . $db->addQuotes( '' );
407 }
408 }
409
410 $conds = array_merge( $conds, $this->getUserRelatedConds( $db, $user, $options ) );
411
412 $deletedPageLogCond = $this->getExtraDeletedPageLogEntryRelatedCond( $db, $user );
413 if ( $deletedPageLogCond ) {
414 $conds[] = $deletedPageLogCond;
415 }
416
417 return $conds;
418 }
419
420 private function getWatchlistOwnerId( User $user, array $options ) {
421 if ( array_key_exists( 'watchlistOwner', $options ) ) {
423 $watchlistOwner = $options['watchlistOwner'];
424 $ownersToken = $watchlistOwner->getOption( 'watchlisttoken' );
425 $token = $options['watchlistOwnerToken'];
426 if ( $ownersToken == '' || !hash_equals( $ownersToken, $token ) ) {
427 throw ApiUsageException::newWithMessage( null, 'apierror-bad-watchlist-token', 'bad_wltoken' );
428 }
429 return $watchlistOwner->getId();
430 }
431 return $user->getId();
432 }
433
435 $conds = [];
436
437 if ( in_array( self::FILTER_MINOR, $options['filters'] ) ) {
438 $conds[] = 'rc_minor != 0';
439 } elseif ( in_array( self::FILTER_NOT_MINOR, $options['filters'] ) ) {
440 $conds[] = 'rc_minor = 0';
441 }
442
443 if ( in_array( self::FILTER_BOT, $options['filters'] ) ) {
444 $conds[] = 'rc_bot != 0';
445 } elseif ( in_array( self::FILTER_NOT_BOT, $options['filters'] ) ) {
446 $conds[] = 'rc_bot = 0';
447 }
448
449 if ( in_array( self::FILTER_ANON, $options['filters'] ) ) {
450 $conds[] = 'rc_user = 0';
451 } elseif ( in_array( self::FILTER_NOT_ANON, $options['filters'] ) ) {
452 $conds[] = 'rc_user != 0';
453 }
454
455 if ( $user->useRCPatrol() || $user->useNPPatrol() ) {
456 // TODO: not sure if this should simply ignore patrolled filters if user does not have the patrol
457 // right, or maybe rather fail loud at this point, same as e.g. ApiQueryWatchlist does?
458 if ( in_array( self::FILTER_PATROLLED, $options['filters'] ) ) {
459 $conds[] = 'rc_patrolled != 0';
460 } elseif ( in_array( self::FILTER_NOT_PATROLLED, $options['filters'] ) ) {
461 $conds[] = 'rc_patrolled = 0';
462 }
463 }
464
465 if ( in_array( self::FILTER_UNREAD, $options['filters'] ) ) {
466 $conds[] = 'rc_timestamp >= wl_notificationtimestamp';
467 } elseif ( in_array( self::FILTER_NOT_UNREAD, $options['filters'] ) ) {
468 // TODO: should this be changed to use Database::makeList?
469 $conds[] = 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp';
470 }
471
472 return $conds;
473 }
474
475 private function getStartEndConds( IDatabase $db, array $options ) {
476 if ( !isset( $options['start'] ) && !isset( $options['end'] ) ) {
477 return [];
478 }
479
480 $conds = [];
481
482 if ( isset( $options['start'] ) ) {
483 $after = $options['dir'] === self::DIR_OLDER ? '<=' : '>=';
484 $conds[] = 'rc_timestamp ' . $after . ' ' .
485 $db->addQuotes( $db->timestamp( $options['start'] ) );
486 }
487 if ( isset( $options['end'] ) ) {
488 $before = $options['dir'] === self::DIR_OLDER ? '>=' : '<=';
489 $conds[] = 'rc_timestamp ' . $before . ' ' .
490 $db->addQuotes( $db->timestamp( $options['end'] ) );
491 }
492
493 return $conds;
494 }
495
496 private function getUserRelatedConds( IDatabase $db, User $user, array $options ) {
497 if ( !array_key_exists( 'onlyByUser', $options ) && !array_key_exists( 'notByUser', $options ) ) {
498 return [];
499 }
500
501 $conds = [];
502
503 if ( array_key_exists( 'onlyByUser', $options ) ) {
504 $conds['rc_user_text'] = $options['onlyByUser'];
505 } elseif ( array_key_exists( 'notByUser', $options ) ) {
506 $conds[] = 'rc_user_text != ' . $db->addQuotes( $options['notByUser'] );
507 }
508
509 // Avoid brute force searches (T19342)
510 $bitmask = 0;
511 if ( !$user->isAllowed( 'deletedhistory' ) ) {
512 $bitmask = Revision::DELETED_USER;
513 } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
515 }
516 if ( $bitmask ) {
517 $conds[] = $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask";
518 }
519
520 return $conds;
521 }
522
524 // LogPage::DELETED_ACTION hides the affected page, too. So hide those
525 // entirely from the watchlist, or someone could guess the title.
526 $bitmask = 0;
527 if ( !$user->isAllowed( 'deletedhistory' ) ) {
528 $bitmask = LogPage::DELETED_ACTION;
529 } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
531 }
532 if ( $bitmask ) {
533 return $db->makeList( [
534 'rc_type != ' . RC_LOG,
535 $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
536 ], LIST_OR );
537 }
538 return '';
539 }
540
541 private function getStartFromConds( IDatabase $db, array $options, array $startFrom ) {
542 $op = $options['dir'] === self::DIR_OLDER ? '<' : '>';
543 list( $rcTimestamp, $rcId ) = $startFrom;
544 $rcTimestamp = $db->addQuotes( $db->timestamp( $rcTimestamp ) );
545 $rcId = (int)$rcId;
546 return $db->makeList(
547 [
548 "rc_timestamp $op $rcTimestamp",
549 $db->makeList(
550 [
551 "rc_timestamp = $rcTimestamp",
552 "rc_id $op= $rcId"
553 ],
555 )
556 ],
557 LIST_OR
558 );
559 }
560
562 $conds = [ 'wl_user' => $user->getId() ];
563 if ( $options['namespaceIds'] ) {
564 $conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
565 }
566 if ( isset( $options['filter'] ) ) {
567 $filter = $options['filter'];
568 if ( $filter === self::FILTER_CHANGED ) {
569 $conds[] = 'wl_notificationtimestamp IS NOT NULL';
570 } else {
571 $conds[] = 'wl_notificationtimestamp IS NULL';
572 }
573 }
574
575 if ( isset( $options['from'] ) ) {
576 $op = $options['sort'] === self::SORT_ASC ? '>' : '<';
577 $conds[] = $this->getFromUntilTargetConds( $db, $options['from'], $op );
578 }
579 if ( isset( $options['until'] ) ) {
580 $op = $options['sort'] === self::SORT_ASC ? '<' : '>';
581 $conds[] = $this->getFromUntilTargetConds( $db, $options['until'], $op );
582 }
583 if ( isset( $options['startFrom'] ) ) {
584 $op = $options['sort'] === self::SORT_ASC ? '>' : '<';
585 $conds[] = $this->getFromUntilTargetConds( $db, $options['startFrom'], $op );
586 }
587
588 return $conds;
589 }
590
600 private function getFromUntilTargetConds( IDatabase $db, LinkTarget $target, $op ) {
601 return $db->makeList(
602 [
603 "wl_namespace $op " . $target->getNamespace(),
604 $db->makeList(
605 [
606 'wl_namespace = ' . $target->getNamespace(),
607 "wl_title $op= " . $db->addQuotes( $target->getDBkey() )
608 ],
610 )
611 ],
612 LIST_OR
613 );
614 }
615
617 $dbOptions = [];
618
619 if ( array_key_exists( 'dir', $options ) ) {
620 $sort = $options['dir'] === self::DIR_OLDER ? ' DESC' : '';
621 $dbOptions['ORDER BY'] = [ 'rc_timestamp' . $sort, 'rc_id' . $sort ];
622 }
623
624 if ( array_key_exists( 'limit', $options ) ) {
625 $dbOptions['LIMIT'] = (int)$options['limit'] + 1;
626 }
627
628 return $dbOptions;
629 }
630
632 $dbOptions = [];
633 if ( array_key_exists( 'sort', $options ) ) {
634 $dbOptions['ORDER BY'] = [
635 "wl_namespace {$options['sort']}",
636 "wl_title {$options['sort']}"
637 ];
638 if ( count( $options['namespaceIds'] ) === 1 ) {
639 $dbOptions['ORDER BY'] = "wl_title {$options['sort']}";
640 }
641 }
642 if ( array_key_exists( 'limit', $options ) ) {
643 $dbOptions['LIMIT'] = (int)$options['limit'];
644 }
645 return $dbOptions;
646 }
647
649 $joinConds = [
650 'watchlist' => [ 'INNER JOIN',
651 [
652 'wl_namespace=rc_namespace',
653 'wl_title=rc_title'
654 ]
655 ]
656 ];
657 if ( !$options['allRevisions'] ) {
658 $joinConds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
659 }
660 return $joinConds;
661 }
662
663}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static newWithMessage(ApiBase $module=null, $msg, $code=null, $data=null, $httpCode=0)
const DELETED_RESTRICTED
Definition LogPage.php:35
const DELETED_ACTION
Definition LogPage.php:32
const DELETED_USER
Definition Revision.php:92
const DELETED_RESTRICTED
Definition Revision.php:93
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:50
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)
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...
__construct(LoadBalancer $loadBalancer)
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.
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
$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 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
const RC_NEW
Definition Defines.php:141
const LIST_OR
Definition Defines.php:44
const RC_LOG
Definition Defines.php:142
const RC_EXTERNAL
Definition Defines.php:143
const LIST_AND
Definition Defines.php:41
const RC_EDIT
Definition Defines.php:140
const RC_CATEGORIZE
Definition Defines.php:144
the array() calling protocol came about after MediaWiki 1.4rc1.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
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:1018
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 and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1143
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 and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1102
processing should stop and the error should be shown to the user * false
Definition hooks.txt:189
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:37
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:40
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