MediaWiki REL1_28
WatchedItemQueryService.php
Go to the documentation of this file.
1<?php
2
4use Wikimedia\Assert\Assert;
5
17
18 const DIR_OLDER = 'older';
19 const DIR_NEWER = 'newer';
20
21 const INCLUDE_FLAGS = 'flags';
22 const INCLUDE_USER = 'user';
23 const INCLUDE_USER_ID = 'userid';
24 const INCLUDE_COMMENT = 'comment';
25 const INCLUDE_PATROL_INFO = 'patrol';
26 const INCLUDE_SIZES = 'sizes';
27 const INCLUDE_LOG_INFO = 'loginfo';
28
29 // FILTER_* constants are part of public API (are used in ApiQueryWatchlist and
30 // ApiQueryWatchlistRaw classes) and should not be changed.
31 // Changing values of those constants will result in a breaking change in the API
32 const FILTER_MINOR = 'minor';
33 const FILTER_NOT_MINOR = '!minor';
34 const FILTER_BOT = 'bot';
35 const FILTER_NOT_BOT = '!bot';
36 const FILTER_ANON = 'anon';
37 const FILTER_NOT_ANON = '!anon';
38 const FILTER_PATROLLED = 'patrolled';
39 const FILTER_NOT_PATROLLED = '!patrolled';
40 const FILTER_UNREAD = 'unread';
41 const FILTER_NOT_UNREAD = '!unread';
42 const FILTER_CHANGED = 'changed';
43 const FILTER_NOT_CHANGED = '!changed';
44
45 const SORT_ASC = 'ASC';
46 const SORT_DESC = 'DESC';
47
52
53 public function __construct( LoadBalancer $loadBalancer ) {
54 $this->loadBalancer = $loadBalancer;
55 }
56
61 private function getConnection() {
62 return $this->loadBalancer->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
63 }
64
111 $options += [
112 'includeFields' => [],
113 'namespaceIds' => [],
114 'filters' => [],
115 'allRevisions' => false,
116 'usedInGenerator' => false
117 ];
118
119 Assert::parameter(
120 !isset( $options['rcTypes'] )
121 || !array_diff( $options['rcTypes'], [ RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL, RC_CATEGORIZE ] ),
122 '$options[\'rcTypes\']',
123 'must be an array containing only: RC_EDIT, RC_NEW, RC_LOG, RC_EXTERNAL and/or RC_CATEGORIZE'
124 );
125 Assert::parameter(
126 !isset( $options['dir'] ) || in_array( $options['dir'], [ self::DIR_OLDER, self::DIR_NEWER ] ),
127 '$options[\'dir\']',
128 'must be DIR_OLDER or DIR_NEWER'
129 );
130 Assert::parameter(
131 !isset( $options['start'] ) && !isset( $options['end'] ) && !isset( $options['startFrom'] )
132 || isset( $options['dir'] ),
133 '$options[\'dir\']',
134 'must be provided when providing any of options: start, end, startFrom'
135 );
136 Assert::parameter(
137 !isset( $options['startFrom'] )
138 || ( is_array( $options['startFrom'] ) && count( $options['startFrom'] ) === 2 ),
139 '$options[\'startFrom\']',
140 'must be a two-element array'
141 );
142 if ( array_key_exists( 'watchlistOwner', $options ) ) {
143 Assert::parameterType(
144 User::class,
145 $options['watchlistOwner'],
146 '$options[\'watchlistOwner\']'
147 );
148 Assert::parameter(
149 isset( $options['watchlistOwnerToken'] ),
150 '$options[\'watchlistOwnerToken\']',
151 'must be provided when providing watchlistOwner option'
152 );
153 }
154
155 $tables = [ 'recentchanges', 'watchlist' ];
156 if ( !$options['allRevisions'] ) {
157 $tables[] = 'page';
158 }
159
160 $db = $this->getConnection();
161
163 $conds = $this->getWatchedItemsWithRCInfoQueryConds( $db, $user, $options );
166
167 $res = $db->select(
168 $tables,
169 $fields,
170 $conds,
171 __METHOD__,
172 $dbOptions,
173 $joinConds
174 );
175
176 $items = [];
177 foreach ( $res as $row ) {
178 $items[] = [
179 new WatchedItem(
180 $user,
181 new TitleValue( (int)$row->rc_namespace, $row->rc_title ),
182 $row->wl_notificationtimestamp
183 ),
184 $this->getRecentChangeFieldsFromRow( $row )
185 ];
186 }
187
188 return $items;
189 }
190
210 public function getWatchedItemsForUser( User $user, array $options = [] ) {
211 if ( $user->isAnon() ) {
212 // TODO: should this just return an empty array or rather complain loud at this point
213 // as e.g. ApiBase::getWatchlistUser does?
214 return [];
215 }
216
217 $options += [ 'namespaceIds' => [] ];
218
219 Assert::parameter(
220 !isset( $options['sort'] ) || in_array( $options['sort'], [ self::SORT_ASC, self::SORT_DESC ] ),
221 '$options[\'sort\']',
222 'must be SORT_ASC or SORT_DESC'
223 );
224 Assert::parameter(
225 !isset( $options['filter'] ) || in_array(
226 $options['filter'], [ self::FILTER_CHANGED, self::FILTER_NOT_CHANGED ]
227 ),
228 '$options[\'filter\']',
229 'must be FILTER_CHANGED or FILTER_NOT_CHANGED'
230 );
231 Assert::parameter(
232 !isset( $options['from'] ) && !isset( $options['until'] ) && !isset( $options['startFrom'] )
233 || isset( $options['sort'] ),
234 '$options[\'sort\']',
235 'must be provided if any of "from", "until", "startFrom" options is provided'
236 );
237
238 $db = $this->getConnection();
239
240 $conds = $this->getWatchedItemsForUserQueryConds( $db, $user, $options );
241 $dbOptions = $this->getWatchedItemsForUserQueryDbOptions( $options );
242
243 $res = $db->select(
244 'watchlist',
245 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
246 $conds,
247 __METHOD__,
248 $dbOptions
249 );
250
251 $watchedItems = [];
252 foreach ( $res as $row ) {
253 // todo these could all be cached at some point?
254 $watchedItems[] = new WatchedItem(
255 $user,
256 new TitleValue( (int)$row->wl_namespace, $row->wl_title ),
257 $row->wl_notificationtimestamp
258 );
259 }
260
261 return $watchedItems;
262 }
263
264 private function getRecentChangeFieldsFromRow( stdClass $row ) {
265 // This can be simplified to single array_filter call filtering by key value,
266 // once we stop supporting PHP 5.5
267 $allFields = get_object_vars( $row );
268 $rcKeys = array_filter(
269 array_keys( $allFields ),
270 function( $key ) {
271 return substr( $key, 0, 3 ) === 'rc_';
272 }
273 );
274 return array_intersect_key( $allFields, array_flip( $rcKeys ) );
275 }
276
278 $fields = [
279 'rc_id',
280 'rc_namespace',
281 'rc_title',
282 'rc_timestamp',
283 'rc_type',
284 'rc_deleted',
285 'wl_notificationtimestamp'
286 ];
287
288 $rcIdFields = [
289 'rc_cur_id',
290 'rc_this_oldid',
291 'rc_last_oldid',
292 ];
293 if ( $options['usedInGenerator'] ) {
294 if ( $options['allRevisions'] ) {
295 $rcIdFields = [ 'rc_this_oldid' ];
296 } else {
297 $rcIdFields = [ 'rc_cur_id' ];
298 }
299 }
300 $fields = array_merge( $fields, $rcIdFields );
301
302 if ( in_array( self::INCLUDE_FLAGS, $options['includeFields'] ) ) {
303 $fields = array_merge( $fields, [ 'rc_type', 'rc_minor', 'rc_bot' ] );
304 }
305 if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ) {
306 $fields[] = 'rc_user_text';
307 }
308 if ( in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ) {
309 $fields[] = 'rc_user';
310 }
311 if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
312 $fields[] = 'rc_comment';
313 }
314 if ( in_array( self::INCLUDE_PATROL_INFO, $options['includeFields'] ) ) {
315 $fields = array_merge( $fields, [ 'rc_patrolled', 'rc_log_type' ] );
316 }
317 if ( in_array( self::INCLUDE_SIZES, $options['includeFields'] ) ) {
318 $fields = array_merge( $fields, [ 'rc_old_len', 'rc_new_len' ] );
319 }
320 if ( in_array( self::INCLUDE_LOG_INFO, $options['includeFields'] ) ) {
321 $fields = array_merge( $fields, [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ] );
322 }
323
324 return $fields;
325 }
326
328 IDatabase $db,
329 User $user,
331 ) {
332 $watchlistOwnerId = $this->getWatchlistOwnerId( $user, $options );
333 $conds = [ 'wl_user' => $watchlistOwnerId ];
334
335 if ( !$options['allRevisions'] ) {
336 $conds[] = $db->makeList(
337 [ 'rc_this_oldid=page_latest', 'rc_type=' . RC_LOG ],
338 LIST_OR
339 );
340 }
341
342 if ( $options['namespaceIds'] ) {
343 $conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
344 }
345
346 if ( array_key_exists( 'rcTypes', $options ) ) {
347 $conds['rc_type'] = array_map( 'intval', $options['rcTypes'] );
348 }
349
350 $conds = array_merge(
351 $conds,
353 );
354
355 $conds = array_merge( $conds, $this->getStartEndConds( $db, $options ) );
356
357 if ( !isset( $options['start'] ) && !isset( $options['end'] ) ) {
358 if ( $db->getType() === 'mysql' ) {
359 // This is an index optimization for mysql
360 $conds[] = "rc_timestamp > ''";
361 }
362 }
363
364 $conds = array_merge( $conds, $this->getUserRelatedConds( $db, $user, $options ) );
365
366 $deletedPageLogCond = $this->getExtraDeletedPageLogEntryRelatedCond( $db, $user );
367 if ( $deletedPageLogCond ) {
368 $conds[] = $deletedPageLogCond;
369 }
370
371 if ( array_key_exists( 'startFrom', $options ) ) {
372 $conds[] = $this->getStartFromConds( $db, $options );
373 }
374
375 return $conds;
376 }
377
378 private function getWatchlistOwnerId( User $user, array $options ) {
379 if ( array_key_exists( 'watchlistOwner', $options ) ) {
381 $watchlistOwner = $options['watchlistOwner'];
382 $ownersToken = $watchlistOwner->getOption( 'watchlisttoken' );
383 $token = $options['watchlistOwnerToken'];
384 if ( $ownersToken == '' || !hash_equals( $ownersToken, $token ) ) {
385 throw new UsageException(
386 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
387 'bad_wltoken'
388 );
389 }
390 return $watchlistOwner->getId();
391 }
392 return $user->getId();
393 }
394
396 $conds = [];
397
398 if ( in_array( self::FILTER_MINOR, $options['filters'] ) ) {
399 $conds[] = 'rc_minor != 0';
400 } elseif ( in_array( self::FILTER_NOT_MINOR, $options['filters'] ) ) {
401 $conds[] = 'rc_minor = 0';
402 }
403
404 if ( in_array( self::FILTER_BOT, $options['filters'] ) ) {
405 $conds[] = 'rc_bot != 0';
406 } elseif ( in_array( self::FILTER_NOT_BOT, $options['filters'] ) ) {
407 $conds[] = 'rc_bot = 0';
408 }
409
410 if ( in_array( self::FILTER_ANON, $options['filters'] ) ) {
411 $conds[] = 'rc_user = 0';
412 } elseif ( in_array( self::FILTER_NOT_ANON, $options['filters'] ) ) {
413 $conds[] = 'rc_user != 0';
414 }
415
416 if ( $user->useRCPatrol() || $user->useNPPatrol() ) {
417 // TODO: not sure if this should simply ignore patrolled filters if user does not have the patrol
418 // right, or maybe rather fail loud at this point, same as e.g. ApiQueryWatchlist does?
419 if ( in_array( self::FILTER_PATROLLED, $options['filters'] ) ) {
420 $conds[] = 'rc_patrolled != 0';
421 } elseif ( in_array( self::FILTER_NOT_PATROLLED, $options['filters'] ) ) {
422 $conds[] = 'rc_patrolled = 0';
423 }
424 }
425
426 if ( in_array( self::FILTER_UNREAD, $options['filters'] ) ) {
427 $conds[] = 'rc_timestamp >= wl_notificationtimestamp';
428 } elseif ( in_array( self::FILTER_NOT_UNREAD, $options['filters'] ) ) {
429 // TODO: should this be changed to use Database::makeList?
430 $conds[] = 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp';
431 }
432
433 return $conds;
434 }
435
436 private function getStartEndConds( IDatabase $db, array $options ) {
437 if ( !isset( $options['start'] ) && ! isset( $options['end'] ) ) {
438 return [];
439 }
440
441 $conds = [];
442
443 if ( isset( $options['start'] ) ) {
444 $after = $options['dir'] === self::DIR_OLDER ? '<=' : '>=';
445 $conds[] = 'rc_timestamp ' . $after . ' ' .
446 $db->addQuotes( $db->timestamp( $options['start'] ) );
447 }
448 if ( isset( $options['end'] ) ) {
449 $before = $options['dir'] === self::DIR_OLDER ? '>=' : '<=';
450 $conds[] = 'rc_timestamp ' . $before . ' ' .
451 $db->addQuotes( $db->timestamp( $options['end'] ) );
452 }
453
454 return $conds;
455 }
456
457 private function getUserRelatedConds( IDatabase $db, User $user, array $options ) {
458 if ( !array_key_exists( 'onlyByUser', $options ) && !array_key_exists( 'notByUser', $options ) ) {
459 return [];
460 }
461
462 $conds = [];
463
464 if ( array_key_exists( 'onlyByUser', $options ) ) {
465 $conds['rc_user_text'] = $options['onlyByUser'];
466 } elseif ( array_key_exists( 'notByUser', $options ) ) {
467 $conds[] = 'rc_user_text != ' . $db->addQuotes( $options['notByUser'] );
468 }
469
470 // Avoid brute force searches (bug 17342)
471 $bitmask = 0;
472 if ( !$user->isAllowed( 'deletedhistory' ) ) {
473 $bitmask = Revision::DELETED_USER;
474 } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
476 }
477 if ( $bitmask ) {
478 $conds[] = $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask";
479 }
480
481 return $conds;
482 }
483
485 // LogPage::DELETED_ACTION hides the affected page, too. So hide those
486 // entirely from the watchlist, or someone could guess the title.
487 $bitmask = 0;
488 if ( !$user->isAllowed( 'deletedhistory' ) ) {
489 $bitmask = LogPage::DELETED_ACTION;
490 } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
492 }
493 if ( $bitmask ) {
494 return $db->makeList( [
495 'rc_type != ' . RC_LOG,
496 $db->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
497 ], LIST_OR );
498 }
499 return '';
500 }
501
502 private function getStartFromConds( IDatabase $db, array $options ) {
503 $op = $options['dir'] === self::DIR_OLDER ? '<' : '>';
504 list( $rcTimestamp, $rcId ) = $options['startFrom'];
505 $rcTimestamp = $db->addQuotes( $db->timestamp( $rcTimestamp ) );
506 $rcId = (int)$rcId;
507 return $db->makeList(
508 [
509 "rc_timestamp $op $rcTimestamp",
510 $db->makeList(
511 [
512 "rc_timestamp = $rcTimestamp",
513 "rc_id $op= $rcId"
514 ],
516 )
517 ],
518 LIST_OR
519 );
520 }
521
523 $conds = [ 'wl_user' => $user->getId() ];
524 if ( $options['namespaceIds'] ) {
525 $conds['wl_namespace'] = array_map( 'intval', $options['namespaceIds'] );
526 }
527 if ( isset( $options['filter'] ) ) {
528 $filter = $options['filter'];
529 if ( $filter === self::FILTER_CHANGED ) {
530 $conds[] = 'wl_notificationtimestamp IS NOT NULL';
531 } else {
532 $conds[] = 'wl_notificationtimestamp IS NULL';
533 }
534 }
535
536 if ( isset( $options['from'] ) ) {
537 $op = $options['sort'] === self::SORT_ASC ? '>' : '<';
538 $conds[] = $this->getFromUntilTargetConds( $db, $options['from'], $op );
539 }
540 if ( isset( $options['until'] ) ) {
541 $op = $options['sort'] === self::SORT_ASC ? '<' : '>';
542 $conds[] = $this->getFromUntilTargetConds( $db, $options['until'], $op );
543 }
544 if ( isset( $options['startFrom'] ) ) {
545 $op = $options['sort'] === self::SORT_ASC ? '>' : '<';
546 $conds[] = $this->getFromUntilTargetConds( $db, $options['startFrom'], $op );
547 }
548
549 return $conds;
550 }
551
561 private function getFromUntilTargetConds( IDatabase $db, LinkTarget $target, $op ) {
562 return $db->makeList(
563 [
564 "wl_namespace $op " . $target->getNamespace(),
565 $db->makeList(
566 [
567 'wl_namespace = ' . $target->getNamespace(),
568 "wl_title $op= " . $db->addQuotes( $target->getDBkey() )
569 ],
571 )
572 ],
573 LIST_OR
574 );
575 }
576
578 $dbOptions = [];
579
580 if ( array_key_exists( 'dir', $options ) ) {
581 $sort = $options['dir'] === self::DIR_OLDER ? ' DESC' : '';
582 $dbOptions['ORDER BY'] = [ 'rc_timestamp' . $sort, 'rc_id' . $sort ];
583 }
584
585 if ( array_key_exists( 'limit', $options ) ) {
586 $dbOptions['LIMIT'] = (int)$options['limit'];
587 }
588
589 return $dbOptions;
590 }
591
593 $dbOptions = [];
594 if ( array_key_exists( 'sort', $options ) ) {
595 $dbOptions['ORDER BY'] = [
596 "wl_namespace {$options['sort']}",
597 "wl_title {$options['sort']}"
598 ];
599 if ( count( $options['namespaceIds'] ) === 1 ) {
600 $dbOptions['ORDER BY'] = "wl_title {$options['sort']}";
601 }
602 }
603 if ( array_key_exists( 'limit', $options ) ) {
604 $dbOptions['LIMIT'] = (int)$options['limit'];
605 }
606 return $dbOptions;
607 }
608
610 $joinConds = [
611 'watchlist' => [ 'INNER JOIN',
612 [
613 'wl_namespace=rc_namespace',
614 'wl_title=rc_title'
615 ]
616 ]
617 ];
618 if ( !$options['allRevisions'] ) {
619 $joinConds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
620 }
621 return $joinConds;
622 }
623
624}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Database connection, tracking, load balancing, and transaction manager for a cluster.
const DELETED_RESTRICTED
Definition LogPage.php:36
const DELETED_ACTION
Definition LogPage.php:33
const DELETED_USER
Definition Revision.php:87
const DELETED_RESTRICTED
Definition Revision.php:88
Represents a page (or page fragment) title within MediaWiki.
This exception will be thrown when dieUsage is called to stop module execution.
Definition ApiMain.php:1860
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
getWatchedItemsForUser(User $user, array $options=[])
For simple listing of user's watchlist items, see WatchedItemStore::getWatchedItemsForUser.
getExtraDeletedPageLogEntryRelatedCond(IDatabase $db, User $user)
getStartFromConds(IDatabase $db, array $options)
getUserRelatedConds(IDatabase $db, User $user, array $options)
getWatchedItemsWithRCInfoQueryDbOptions(array $options)
getWatchedItemsWithRecentChangeInfo(User $user, 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)
getWatchedItemsWithRCInfoQueryJoinConds(array $options)
getWatchedItemsForUserQueryDbOptions(array $options)
Representation of a pair of user and title for watchlist entries.
$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:137
const LIST_OR
Definition Defines.php:38
const RC_LOG
Definition Defines.php:138
const RC_EXTERNAL
Definition Defines.php:139
const LIST_AND
Definition Defines.php:35
const RC_EDIT
Definition Defines.php:136
const RC_CATEGORIZE
Definition Defines.php:140
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 e g Watchlist & $tables
Definition hooks.txt:1028
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist 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:1096
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
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:34
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
addQuotes( $s)
Adds quotes and backslashes.
bitAnd( $fieldLeft, $fieldRight)
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
$sort
const DB_REPLICA
Definition defines.php:22