Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
52.70% covered (warning)
52.70%
127 / 241
43.48% covered (danger)
43.48%
10 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
WikiExporter
52.70% covered (warning)
52.70%
127 / 241
43.48% covered (danger)
43.48%
10 / 23
670.37
0.00% covered (danger)
0.00%
0 / 1
 schemaVersion
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 setSchemaVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOutputSink
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 openStream
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 closeStream
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 allPages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pagesByRange
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 revsByRange
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 pageByTitle
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 pageByName
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
2.15
 pagesByName
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 allLogs
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 logsByRange
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 do_list_authors
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 dumpFrom
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 dumpLogs
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
30
 dumpPages
69.23% covered (warning)
69.23%
54 / 78
0.00% covered (danger)
0.00%
0 / 1
36.10
 outputPageStreamBatch
63.33% covered (warning)
63.33%
19 / 30
0.00% covered (danger)
0.00%
0 / 1
19.10
 getSlotRowBatch
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 finishPageStreamOutput
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 outputLogStream
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 reloadDBConfig
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Base class for exporting
4 *
5 * Copyright © 2003, 2005, 2006 Brooke Vibber <bvibber@wikimedia.org>
6 * https://www.mediawiki.org/
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * http://www.gnu.org/copyleft/gpl.html
22 *
23 * @file
24 */
25
26/**
27 * @defgroup Dump Dump
28 */
29
30use MediaWiki\CommentStore\CommentStore;
31use MediaWiki\HookContainer\HookContainer;
32use MediaWiki\HookContainer\HookRunner;
33use MediaWiki\MainConfigNames;
34use MediaWiki\MediaWikiServices;
35use MediaWiki\Page\PageIdentity;
36use MediaWiki\Revision\RevisionAccessException;
37use MediaWiki\Revision\RevisionRecord;
38use MediaWiki\Revision\RevisionStore;
39use MediaWiki\Title\MalformedTitleException;
40use MediaWiki\Title\TitleParser;
41use Wikimedia\Rdbms\IReadableDatabase;
42use Wikimedia\Rdbms\IResultWrapper;
43
44/**
45 * @ingroup SpecialPage Dump
46 */
47class WikiExporter {
48    /** @var bool Return distinct author list (when not returning full history) */
49    public $list_authors = false;
50
51    /** @var bool */
52    public $dumpUploads = false;
53
54    /** @var bool */
55    public $dumpUploadFileContents = false;
56
57    /** @var string */
58    public $author_list = "";
59
60    public const FULL = 1;
61    public const CURRENT = 2;
62    public const STABLE = 4; // extension defined
63    public const LOGS = 8;
64    public const RANGE = 16;
65
66    public const TEXT = XmlDumpWriter::WRITE_CONTENT;
67    public const STUB = XmlDumpWriter::WRITE_STUB;
68
69    protected const BATCH_SIZE = 50000;
70
71    /** @var int */
72    public $text;
73
74    /** @var DumpOutput */
75    public $sink;
76
77    /** @var XmlDumpWriter */
78    private $writer;
79
80    /** @var IReadableDatabase */
81    protected $db;
82
83    /** @var array|int */
84    protected $history;
85
86    /** @var array|null */
87    protected $limitNamespaces;
88
89    /** @var RevisionStore */
90    private $revisionStore;
91
92    /** @var TitleParser */
93    private $titleParser;
94
95    /** @var HookRunner */
96    private $hookRunner;
97
98    /** @var CommentStore */
99    private $commentStore;
100
101    /**
102     * Returns the default export schema version, as defined by the XmlDumpSchemaVersion setting.
103     * @return string
104     */
105    public static function schemaVersion() {
106        return MediaWikiServices::getInstance()->getMainConfig()->get(
107            MainConfigNames::XmlDumpSchemaVersion );
108    }
109
110    /**
111     * @param IReadableDatabase $db
112     * @param CommentStore $commentStore
113     * @param HookContainer $hookContainer
114     * @param RevisionStore $revisionStore
115     * @param TitleParser $titleParser
116     * @param int|array $history One of WikiExporter::FULL, WikiExporter::CURRENT,
117     *   WikiExporter::RANGE or WikiExporter::STABLE, or an associative array:
118     *   - offset: non-inclusive offset at which to start the query
119     *   - limit: maximum number of rows to return
120     *   - dir: "asc" or "desc" timestamp order
121     * @param int $text One of WikiExporter::TEXT or WikiExporter::STUB
122     * @param null|array $limitNamespaces List of namespace numbers to limit results
123     */
124    public function __construct(
125        $db,
126        CommentStore $commentStore,
127        HookContainer $hookContainer,
128        RevisionStore $revisionStore,
129        TitleParser $titleParser,
130        $history = self::CURRENT,
131        $text = self::TEXT,
132        $limitNamespaces = null
133    ) {
134        $this->db = $db;
135        $this->commentStore = $commentStore;
136        $this->history = $history;
137        $this->writer = new XmlDumpWriter(
138            $text,
139            self::schemaVersion(),
140            $hookContainer,
141            $commentStore
142        );
143        $this->sink = new DumpOutput();
144        $this->text = $text;
145        $this->limitNamespaces = $limitNamespaces;
146        $this->hookRunner = new HookRunner( $hookContainer );
147        $this->revisionStore = $revisionStore;
148        $this->titleParser = $titleParser;
149    }
150
151    /**
152     * @param string $schemaVersion which schema version the generated XML should comply to.
153     * One of the values from self::$supportedSchemas, using the XML_DUMP_SCHEMA_VERSION_XX
154     * constants.
155     */
156    public function setSchemaVersion( $schemaVersion ) {
157        $this->writer = new XmlDumpWriter( $this->text, $schemaVersion );
158    }
159
160    /**
161     * Set the DumpOutput or DumpFilter object which will receive
162     * various row objects and XML output for filtering. Filters
163     * can be chained or used as callbacks.
164     *
165     * @param DumpOutput|DumpFilter &$sink
166     */
167    public function setOutputSink( &$sink ) {
168        $this->sink =& $sink;
169    }
170
171    public function openStream() {
172        $output = $this->writer->openStream();
173        $this->sink->writeOpenStream( $output );
174    }
175
176    public function closeStream() {
177        $output = $this->writer->closeStream();
178        $this->sink->writeCloseStream( $output );
179    }
180
181    /**
182     * Dumps a series of page and revision records for all pages
183     * in the database, either including complete history or only
184     * the most recent version.
185     */
186    public function allPages() {
187        $this->dumpFrom( '' );
188    }
189
190    /**
191     * Dumps a series of page and revision records for those pages
192     * in the database falling within the page_id range given.
193     * @param int $start Inclusive lower limit (this id is included)
194     * @param int $end Exclusive upper limit (this id is not included)
195     *   If 0, no upper limit.
196     * @param bool $orderRevs order revisions within pages in ascending order
197     */
198    public function pagesByRange( $start, $end, $orderRevs ) {
199        if ( $orderRevs ) {
200            $condition = 'rev_page >= ' . intval( $start );
201            if ( $end ) {
202                $condition .= ' AND rev_page < ' . intval( $end );
203            }
204        } else {
205            $condition = 'page_id >= ' . intval( $start );
206            if ( $end ) {
207                $condition .= ' AND page_id < ' . intval( $end );
208            }
209        }
210        $this->dumpFrom( $condition, $orderRevs );
211    }
212
213    /**
214     * Dumps a series of page and revision records for those pages
215     * in the database with revisions falling within the rev_id range given.
216     * @param int $start Inclusive lower limit (this id is included)
217     * @param int $end Exclusive upper limit (this id is not included)
218     *   If 0, no upper limit.
219     */
220    public function revsByRange( $start, $end ) {
221        $condition = 'rev_id >= ' . intval( $start );
222        if ( $end ) {
223            $condition .= ' AND rev_id < ' . intval( $end );
224        }
225        $this->dumpFrom( $condition );
226    }
227
228    /**
229     * @param PageIdentity $page
230     */
231    public function pageByTitle( PageIdentity $page ) {
232        $this->dumpFrom(
233            'page_namespace=' . $page->getNamespace() .
234            ' AND page_title=' . $this->db->addQuotes( $page->getDBkey() ) );
235    }
236
237    /**
238     * @param string $name
239     */
240    public function pageByName( $name ) {
241        try {
242            $link = $this->titleParser->parseTitle( $name );
243            $this->dumpFrom(
244                'page_namespace=' . $link->getNamespace() .
245                ' AND page_title=' . $this->db->addQuotes( $link->getDBkey() ) );
246        } catch ( MalformedTitleException $ex ) {
247            throw new RuntimeException( "Can't export invalid title" );
248        }
249    }
250
251    /**
252     * @param string[] $names
253     */
254    public function pagesByName( $names ) {
255        foreach ( $names as $name ) {
256            $this->pageByName( $name );
257        }
258    }
259
260    public function allLogs() {
261        $this->dumpFrom( '' );
262    }
263
264    /**
265     * @param int $start
266     * @param int $end
267     */
268    public function logsByRange( $start, $end ) {
269        $condition = 'log_id >= ' . intval( $start );
270        if ( $end ) {
271            $condition .= ' AND log_id < ' . intval( $end );
272        }
273        $this->dumpFrom( $condition );
274    }
275
276    /**
277     * Generates the distinct list of authors of an article
278     * Not called by default (depends on $this->list_authors)
279     * Can be set by Special:Export when not exporting whole history
280     *
281     * @param string $cond
282     */
283    protected function do_list_authors( $cond ) {
284        $this->author_list = "<contributors>";
285        // rev_deleted
286
287        $res = $this->revisionStore->newSelectQueryBuilder( $this->db )
288            ->joinPage()
289            ->distinct()
290            ->where( $this->db->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0' )
291            ->andWhere( $cond )
292            ->caller( __METHOD__ )->fetchResultSet();
293
294        foreach ( $res as $row ) {
295            $this->author_list .= "<contributor>" .
296                "<username>" .
297                htmlspecialchars( $row->rev_user_text ) .
298                "</username>" .
299                "<id>" .
300                ( (int)$row->rev_user ) .
301                "</id>" .
302                "</contributor>";
303        }
304        $this->author_list .= "</contributors>";
305    }
306
307    /**
308     * @param string $cond
309     * @param bool $orderRevs
310     */
311    protected function dumpFrom( $cond = '', $orderRevs = false ) {
312        if ( is_int( $this->history ) && ( $this->history & self::LOGS ) ) {
313            $this->dumpLogs( $cond );
314        } else {
315            $this->dumpPages( $cond, $orderRevs );
316        }
317    }
318
319    /**
320     * @param string $cond
321     */
322    protected function dumpLogs( $cond ) {
323        $where = [];
324        # Hide private logs
325        $hideLogs = LogEventsList::getExcludeClause( $this->db );
326        if ( $hideLogs ) {
327            $where[] = $hideLogs;
328        }
329        # Add on any caller specified conditions
330        if ( $cond ) {
331            $where[] = $cond;
332        }
333
334        $commentQuery = $this->commentStore->getJoin( 'log_comment' );
335
336        $tables = array_merge(
337            [ 'logging', 'actor' ], $commentQuery['tables']
338        );
339        $fields = [
340            'log_id', 'log_type', 'log_action', 'log_timestamp', 'log_namespace',
341            'log_title', 'log_params', 'log_deleted', 'actor_user', 'actor_name'
342        ] + $commentQuery['fields'];
343        $options = [
344            'ORDER BY' => 'log_id',
345            'USE INDEX' => [ 'logging' => 'PRIMARY' ],
346            'LIMIT' => self::BATCH_SIZE,
347        ];
348        $joins = [
349            'actor' => [ 'JOIN', 'actor_id=log_actor' ]
350        ] + $commentQuery['joins'];
351
352        $lastLogId = 0;
353        while ( true ) {
354            $result = $this->db->select(
355                $tables,
356                $fields,
357                array_merge( $where, [ 'log_id > ' . intval( $lastLogId ) ] ),
358                __METHOD__,
359                $options,
360                $joins
361            );
362
363            if ( !$result->numRows() ) {
364                break;
365            }
366
367            $lastLogId = $this->outputLogStream( $result );
368            $this->reloadDBConfig();
369        }
370    }
371
372    /**
373     * @param string $cond
374     * @param bool $orderRevs
375     */
376    protected function dumpPages( $cond, $orderRevs ) {
377        $revQuery = $this->revisionStore->getQueryInfo( [ 'page' ] );
378        $slotQuery = $this->revisionStore->getSlotsQueryInfo( [ 'content' ] );
379
380        // We want page primary rather than revision.
381        // We also want to join in the slots and content tables.
382        // NOTE: This means we may get multiple rows per revision, and more rows
383        // than the batch size! Should be ok, since the max number of slots is
384        // fixed and low (dozens at worst).
385        $tables = array_merge( [ 'page' ], array_diff( $revQuery['tables'], [ 'page' ] ) );
386        $tables = array_merge( $tables, array_diff( $slotQuery['tables'], $tables ) );
387        $join = $revQuery['joins'] + [
388                'revision' => $revQuery['joins']['page'],
389                'slots' => [ 'JOIN', [ 'slot_revision_id = rev_id' ] ],
390                'content' => [ 'JOIN', [ 'content_id = slot_content_id' ] ],
391            ];
392        unset( $join['page'] );
393
394        $fields = array_merge( $revQuery['fields'], $slotQuery['fields'] );
395
396        if ( $this->text != self::STUB ) {
397            $fields['_load_content'] = '1';
398        }
399
400        $conds = [];
401        if ( $cond !== '' ) {
402            $conds[] = $cond;
403        }
404        $opts = [ 'ORDER BY' => [ 'rev_page ASC', 'rev_id ASC' ] ];
405        $opts['USE INDEX'] = [];
406
407        $op = '>';
408        if ( is_array( $this->history ) ) {
409            # Time offset/limit for all pages/history...
410            # Set time order
411            if ( $this->history['dir'] == 'asc' ) {
412                $opts['ORDER BY'] = 'rev_timestamp ASC';
413            } else {
414                $op = '<';
415                $opts['ORDER BY'] = 'rev_timestamp DESC';
416            }
417            # Set offset
418            if ( !empty( $this->history['offset'] ) ) {
419                $conds[] = "rev_timestamp $op " .
420                    $this->db->addQuotes( $this->db->timestamp( $this->history['offset'] ) );
421            }
422            # Set query limit
423            if ( !empty( $this->history['limit'] ) ) {
424                $maxRowCount = intval( $this->history['limit'] );
425            }
426        } elseif ( $this->history & self::FULL ) {
427            # Full history dumps...
428            # query optimization for history stub dumps
429            if ( $this->text == self::STUB ) {
430                $opts[] = 'STRAIGHT_JOIN';
431                unset( $join['revision'] );
432                $join['page'] = [ 'JOIN', 'rev_page=page_id' ];
433            }
434        } elseif ( $this->history & self::CURRENT ) {
435            # Latest revision dumps...
436            if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
437                $this->do_list_authors( $cond );
438            }
439            $join['revision'] = [ 'JOIN', 'page_id=rev_page AND page_latest=rev_id' ];
440            $opts[ 'ORDER BY' ] = [ 'page_id ASC' ];
441        } elseif ( $this->history & self::STABLE ) {
442            # "Stable" revision dumps...
443            # Default JOIN, to be overridden...
444            $join['revision'] = [ 'JOIN', 'page_id=rev_page AND page_latest=rev_id' ];
445            # One, and only one hook should set this, and return false
446            if ( $this->hookRunner->onWikiExporter__dumpStableQuery( $tables, $opts, $join ) ) {
447                throw new LogicException( __METHOD__ . " given invalid history dump type." );
448            }
449        } elseif ( $this->history & self::RANGE ) {
450            # Dump of revisions within a specified range.  Condition already set in revsByRange().
451        } else {
452            # Unknown history specification parameter?
453            throw new UnexpectedValueException( __METHOD__ . " given invalid history dump type." );
454        }
455
456        $done = false;
457        $lastRow = null;
458        $revPage = 0;
459        $revId = 0;
460        $rowCount = 0;
461
462        $opts['LIMIT'] = self::BATCH_SIZE;
463
464        $this->hookRunner->onModifyExportQuery(
465            $this->db, $tables, $cond, $opts, $join, $conds );
466
467        while ( !$done ) {
468            // If necessary, impose the overall maximum and stop looping after this iteration.
469            if ( !empty( $maxRowCount ) && $rowCount + self::BATCH_SIZE > $maxRowCount ) {
470                $opts['LIMIT'] = $maxRowCount - $rowCount;
471                $done = true;
472            }
473
474            $queryConds = $conds;
475            $queryConds[] = 'rev_page>' . intval( $revPage ) . ' OR (rev_page=' .
476                intval( $revPage ) . ' AND rev_id' . $op . intval( $revId ) . ')';
477
478            # Do the query and process any results, remembering max ids for the next iteration.
479            $result = $this->db->select(
480                $tables,
481                $fields,
482                $queryConds,
483                __METHOD__,
484                $opts,
485                $join
486            );
487            if ( $result->numRows() > 0 ) {
488                $lastRow = $this->outputPageStreamBatch( $result, $lastRow );
489                $rowCount += $result->numRows();
490                $revPage = $lastRow->rev_page;
491                $revId = $lastRow->rev_id;
492            } else {
493                $done = true;
494            }
495
496            // If we are finished, close off final page element (if any).
497            if ( $done && $lastRow ) {
498                $this->finishPageStreamOutput( $lastRow );
499            }
500
501            if ( !$done ) {
502                $this->reloadDBConfig();
503            }
504        }
505    }
506
507    /**
508     * Runs through a query result set dumping page, revision, and slot records.
509     * The result set should join the page, revision, slots, and content tables,
510     * and be sorted/grouped by page and revision to avoid duplicate page records in the output.
511     *
512     * @param IResultWrapper $results
513     * @param stdClass|null $lastRow the last row output from the previous call (or null if none)
514     * @return stdClass the last row processed
515     */
516    protected function outputPageStreamBatch( $results, $lastRow ) {
517        $rowCarry = null;
518        while ( true ) {
519            $slotRows = $this->getSlotRowBatch( $results, $rowCarry );
520
521            if ( !$slotRows ) {
522                break;
523            }
524
525            // All revision info is present in all slot rows.
526            // Use the first slot row as the revision row.
527            $revRow = $slotRows[0];
528
529            if ( $this->limitNamespaces &&
530                !in_array( $revRow->page_namespace, $this->limitNamespaces ) ) {
531                $lastRow = $revRow;
532                continue;
533            }
534
535            if ( $lastRow === null ||
536                $lastRow->page_namespace !== $revRow->page_namespace ||
537                $lastRow->page_title !== $revRow->page_title ) {
538                if ( $lastRow !== null ) {
539                    $output = '';
540                    if ( $this->dumpUploads ) {
541                        $output .= $this->writer->writeUploads( $lastRow, $this->dumpUploadFileContents );
542                    }
543                    $output .= $this->writer->closePage();
544                    $this->sink->writeClosePage( $output );
545                }
546                $output = $this->writer->openPage( $revRow );
547                $this->sink->writeOpenPage( $revRow, $output );
548            }
549            try {
550                $output = $this->writer->writeRevision( $revRow, $slotRows );
551                $this->sink->writeRevision( $revRow, $output );
552            } catch ( RevisionAccessException $ex ) {
553                MWDebug::warning( 'Problem encountered retrieving rev and slot metadata for'
554                    . ' revision ' . $revRow->rev_id . ': ' . $ex->getMessage() );
555            }
556            $lastRow = $revRow;
557        }
558
559        if ( $rowCarry ) {
560            throw new LogicException( 'Error while processing a stream of slot rows' );
561        }
562
563        // @phan-suppress-next-line PhanTypeMismatchReturnNullable False positive
564        return $lastRow;
565    }
566
567    /**
568     * Returns all slot rows for a revision.
569     * Takes and returns a carry row from the last batch;
570     *
571     * @param IResultWrapper|array $results
572     * @param null|stdClass &$carry A row carried over from the last call to getSlotRowBatch()
573     *
574     * @return stdClass[]
575     */
576    protected function getSlotRowBatch( $results, &$carry = null ) {
577        $slotRows = [];
578        $prev = null;
579
580        if ( $carry ) {
581            $slotRows[] = $carry;
582            $prev = $carry;
583            $carry = null;
584        }
585
586        while ( $row = $results->fetchObject() ) {
587            if ( $prev && $prev->rev_id !== $row->rev_id ) {
588                $carry = $row;
589                break;
590            }
591            $slotRows[] = $row;
592            $prev = $row;
593        }
594
595        return $slotRows;
596    }
597
598    /**
599     * Final page stream output, after all batches are complete
600     *
601     * @param stdClass $lastRow the last row output from the last batch (or null if none)
602     */
603    protected function finishPageStreamOutput( $lastRow ) {
604        $output = '';
605        if ( $this->dumpUploads ) {
606            $output .= $this->writer->writeUploads( $lastRow, $this->dumpUploadFileContents );
607        }
608        $output .= $this->author_list;
609        $output .= $this->writer->closePage();
610        $this->sink->writeClosePage( $output );
611    }
612
613    /**
614     * @param IResultWrapper $resultset
615     * @return int|null the log_id value of the last item output, or null if none
616     */
617    protected function outputLogStream( $resultset ) {
618        foreach ( $resultset as $row ) {
619            $output = $this->writer->writeLogItem( $row );
620            $this->sink->writeLogItem( $row, $output );
621        }
622        return $row->log_id ?? null;
623    }
624
625    /**
626     * Attempt to reload the database configuration, so any changes can take effect.
627     * Dynamic reloading can be enabled by setting $wgLBFactoryConf['configCallback']
628     * to a function that returns an array of any keys that should be updated
629     * in LBFactoryConf.
630     */
631    private function reloadDBConfig() {
632        MediaWikiServices::getInstance()->getDBLoadBalancerFactory()
633            ->autoReconfigure();
634    }
635}