MediaWiki master
SQLPlatform.php
Go to the documentation of this file.
1<?php
7
8use InvalidArgumentException;
9use Psr\Log\LoggerInterface;
10use Psr\Log\NullLogger;
11use RuntimeException;
12use Throwable;
13use Wikimedia\Assert\Assert;
24use Wikimedia\Timestamp\ConvertibleTimestamp;
25use Wikimedia\Timestamp\TimestampFormat as TS;
26
34class SQLPlatform implements ISQLPlatform {
36 protected $tableAliases = [];
39 protected $schemaVars;
40 protected DbQuoter $quoter;
41 protected LoggerInterface $logger;
43 protected $errorLogger;
44
51 public function __construct(
53 ?LoggerInterface $logger = null,
55 $errorLogger = null
56 ) {
57 $this->quoter = $quoter;
58 $this->logger = $logger ?? new NullLogger();
59 $this->currentDomain = $currentDomain ?? DatabaseDomain::newUnspecified();
60 $this->errorLogger = $errorLogger ?? static function ( Throwable $e ) {
61 trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
62 };
63 }
64
66 public function bitNot( $field ) {
67 return "(~$field)";
68 }
69
71 public function bitAnd( $fieldLeft, $fieldRight ) {
72 return "($fieldLeft & $fieldRight)";
73 }
74
76 public function bitOr( $fieldLeft, $fieldRight ) {
77 return "($fieldLeft | $fieldRight)";
78 }
79
81 public function addIdentifierQuotes( $s ) {
82 if ( strcspn( $s, "\0\"`'." ) !== strlen( $s ) ) {
83 throw new DBLanguageError(
84 "Identifier must not contain quote, dot or null characters: got '$s'"
85 );
86 }
87 $quoteChar = $this->getIdentifierQuoteChar();
88 return $quoteChar . $s . $quoteChar;
89 }
90
95 protected function getIdentifierQuoteChar() {
96 return '"';
97 }
98
102 public function buildGreatest( $fields, $values ) {
103 return $this->buildSuperlative( 'GREATEST', $fields, $values );
104 }
105
109 public function buildLeast( $fields, $values ) {
110 return $this->buildSuperlative( 'LEAST', $fields, $values );
111 }
112
127 protected function buildSuperlative( $sqlfunc, $fields, $values ) {
128 $fields = is_array( $fields ) ? $fields : [ $fields ];
129 $values = is_array( $values ) ? $values : [ $values ];
130
131 $encValues = [];
132 foreach ( $fields as $alias => $field ) {
133 if ( is_int( $alias ) ) {
134 $encValues[] = $this->addIdentifierQuotes( $field );
135 } else {
136 $encValues[] = $field; // expression
137 }
138 }
139 foreach ( $values as $value ) {
140 if ( is_int( $value ) || is_float( $value ) ) {
141 $encValues[] = $value;
142 } elseif ( is_string( $value ) ) {
143 $encValues[] = $this->quoter->addQuotes( $value );
144 } elseif ( $value === null ) {
145 throw new DBLanguageError( 'Null value in superlative' );
146 } else {
147 throw new DBLanguageError( 'Unexpected value type in superlative' );
148 }
149 }
150
151 return $sqlfunc . '(' . implode( ',', $encValues ) . ')';
152 }
153
154 public function buildComparison( string $op, array $conds ): string {
155 if ( !in_array( $op, [ '>', '>=', '<', '<=' ] ) ) {
156 throw new InvalidArgumentException( "Comparison operator must be one of '>', '>=', '<', '<='" );
157 }
158 if ( count( $conds ) === 0 ) {
159 throw new InvalidArgumentException( "Empty input" );
160 }
161
162 // Construct a condition string by starting with the least significant part of the index, and
163 // adding more significant parts progressively to the left of the string.
164 //
165 // For example, given $conds = [ 'a' => 4, 'b' => 7, 'c' => 1 ], this will generate a condition
166 // like this:
167 //
168 // WHERE a > 4
169 // OR (a = 4 AND (b > 7
170 // OR (b = 7 AND (c > 1))))
171 //
172 // …which is equivalent to the following, which might be easier to understand:
173 //
174 // WHERE a > 4
175 // OR a = 4 AND b > 7
176 // OR a = 4 AND b = 7 AND c > 1
177 //
178 // …and also equivalent to the following, using tuple comparison syntax, which is most intuitive
179 // but apparently performs worse:
180 //
181 // WHERE (a, b, c) > (4, 7, 1)
182
183 $sql = '';
184 foreach ( array_reverse( $conds ) as $field => $value ) {
185 if ( is_int( $field ) ) {
186 throw new InvalidArgumentException(
187 'Non-associative array passed to buildComparison() (typo?)'
188 );
189 }
190 $encValue = $this->quoter->addQuotes( $value );
191 if ( $sql === '' ) {
192 $sql = "$field $op $encValue";
193 // Change '>=' to '>' etc. for remaining fields, as the equality is handled separately
194 $op = rtrim( $op, '=' );
195 } else {
196 $sql = "$field $op $encValue OR ($field = $encValue AND ($sql))";
197 }
198 }
199 return $sql;
200 }
201
203 public function makeList( array $a, $mode = self::LIST_COMMA ) {
204 $first = true;
205 $list = '';
206 $keyWarning = null;
207
208 foreach ( $a as $field => $value ) {
209 if ( $first ) {
210 $first = false;
211 } else {
212 if ( $mode == self::LIST_AND ) {
213 $list .= ' AND ';
214 } elseif ( $mode == self::LIST_OR ) {
215 $list .= ' OR ';
216 } else {
217 $list .= ',';
218 }
219 }
220
221 if ( ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_numeric( $field ) ) {
222 if ( $value instanceof IExpression ) {
223 $list .= "(" . $value->toSql( $this->quoter ) . ")";
224 } elseif ( is_array( $value ) ) {
225 throw new InvalidArgumentException( __METHOD__ . ": unexpected array value without key" );
226 } elseif ( $value instanceof RawSQLValue ) {
227 throw new InvalidArgumentException( __METHOD__ . ": unexpected raw value without key" );
228 } else {
229 $list .= "($value)";
230 }
231 } elseif ( $value instanceof IExpression ) {
232 if ( $mode == self::LIST_AND || $mode == self::LIST_OR ) {
233 throw new InvalidArgumentException( __METHOD__ . ": unexpected key $field for IExpression value" );
234 } else {
235 throw new InvalidArgumentException( __METHOD__ . ": unexpected IExpression outside WHERE clause" );
236 }
237 } elseif ( $mode == self::LIST_SET && is_numeric( $field ) ) {
238 $list .= "$value";
239 } elseif (
240 ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_array( $value )
241 ) {
242 // Remove null from array to be handled separately if found
243 $includeNull = false;
244 foreach ( array_keys( $value, null, true ) as $nullKey ) {
245 $includeNull = true;
246 unset( $value[$nullKey] );
247 }
248 if ( count( $value ) == 0 && !$includeNull ) {
249 throw new InvalidArgumentException(
250 __METHOD__ . ": empty input for field $field" );
251 } elseif ( count( $value ) == 0 ) {
252 // only check if $field is null
253 $list .= "$field IS NULL";
254 } else {
255 // IN clause contains at least one valid element
256 if ( $includeNull ) {
257 // Group subconditions to ensure correct precedence
258 $list .= '(';
259 }
260 if ( count( $value ) == 1 ) {
261 // Special-case single values, as IN isn't terribly efficient
262 // (but call makeList() so that warnings are emitted if needed)
263 $list .= $field . " = " . $this->makeList( $value );
264 } else {
265 $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
266 }
267 // if null present in array, append IS NULL
268 if ( $includeNull ) {
269 $list .= " OR $field IS NULL)";
270 }
271 }
272 } elseif ( is_array( $value ) ) {
273 throw new InvalidArgumentException( __METHOD__ . ": unexpected nested array" );
274 } elseif ( $value === null ) {
275 if ( $mode == self::LIST_AND || $mode == self::LIST_OR ) {
276 $list .= "$field IS ";
277 } elseif ( $mode == self::LIST_SET ) {
278 $list .= "$field = ";
279 } elseif ( $mode === self::LIST_COMMA && !is_numeric( $field ) ) {
280 $keyWarning ??= [
281 __METHOD__ . ": array key {key} in list of values ignored",
282 [ 'key' => $field, 'exception' => new RuntimeException() ]
283 ];
284 } elseif ( $mode === self::LIST_NAMES && !is_numeric( $field ) ) {
285 $keyWarning ??= [
286 __METHOD__ . ": array key {key} in list of fields ignored",
287 [ 'key' => $field, 'exception' => new RuntimeException() ]
288 ];
289 }
290 $list .= 'NULL';
291 } else {
292 if (
293 $mode == self::LIST_AND || $mode == self::LIST_OR || $mode == self::LIST_SET
294 ) {
295 $list .= "$field = ";
296 } elseif ( $mode === self::LIST_COMMA && !is_numeric( $field ) ) {
297 $keyWarning ??= [
298 __METHOD__ . ": array key {key} in list of values ignored",
299 [ 'key' => $field, 'exception' => new RuntimeException() ]
300 ];
301 } elseif ( $mode === self::LIST_NAMES && !is_numeric( $field ) ) {
302 $keyWarning ??= [
303 __METHOD__ . ": array key {key} in list of fields ignored",
304 [ 'key' => $field, 'exception' => new RuntimeException() ]
305 ];
306 }
307 $list .= $mode == self::LIST_NAMES ? $value : $this->quoter->addQuotes( $value );
308 }
309 }
310
311 if ( $keyWarning ) {
312 // Only log one warning about this per function call, to reduce log spam when a dynamically
313 // generated associative array is passed
314 $this->logger->warning( ...$keyWarning );
315 }
316
317 return $list;
318 }
319
321 public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
322 $conds = [];
323 foreach ( $data as $base => $sub ) {
324 if ( count( $sub ) ) {
325 $conds[] = $this->makeList(
326 [ $baseKey => $base, $subKey => array_map( 'strval', array_keys( $sub ) ) ],
327 self::LIST_AND
328 );
329 }
330 }
331
332 if ( !$conds ) {
333 throw new InvalidArgumentException( "Data for $baseKey and $subKey must be non-empty" );
334 }
335
336 return $this->makeList( $conds, self::LIST_OR );
337 }
338
340 public function factorConds( $condsArray ) {
341 if ( count( $condsArray ) === 0 ) {
342 throw new InvalidArgumentException(
343 __METHOD__ . ": empty condition array" );
344 }
345 $condsByFieldSet = [];
346 foreach ( $condsArray as $conds ) {
347 if ( !count( $conds ) ) {
348 throw new InvalidArgumentException(
349 __METHOD__ . ": empty condition subarray" );
350 }
351 $fieldKey = implode( ',', array_keys( $conds ) );
352 $condsByFieldSet[$fieldKey][] = $conds;
353 }
354 $result = '';
355 foreach ( $condsByFieldSet as $conds ) {
356 if ( $result !== '' ) {
357 $result .= ' OR ';
358 }
359 $result .= $this->factorCondsWithCommonFields( $conds );
360 }
361 return $result;
362 }
363
371 private function factorCondsWithCommonFields( $condsArray ) {
372 $first = $condsArray[array_key_first( $condsArray )];
373 if ( count( $first ) === 1 ) {
374 // IN clause
375 $field = array_key_first( $first );
376 $values = [];
377 foreach ( $condsArray as $conds ) {
378 $values[] = $conds[$field];
379 }
380 return $this->makeList( [ $field => $values ], self::LIST_AND );
381 }
382
383 $field1 = array_key_first( $first );
384 $nullExpressions = [];
385 $expressionsByField1 = [];
386 foreach ( $condsArray as $conds ) {
387 $value1 = $conds[$field1];
388 unset( $conds[$field1] );
389 if ( $value1 === null ) {
390 $nullExpressions[] = $conds;
391 } else {
392 $expressionsByField1[$value1][] = $conds;
393 }
394
395 }
396 $wrap = false;
397 $result = '';
398 foreach ( $expressionsByField1 as $value1 => $expressions ) {
399 if ( $result !== '' ) {
400 $result .= ' OR ';
401 $wrap = true;
402 }
403 $factored = $this->factorCondsWithCommonFields( $expressions );
404 $result .= "($field1 = " . $this->quoter->addQuotes( $value1 ) .
405 " AND $factored)";
406 }
407 if ( count( $nullExpressions ) ) {
408 $factored = $this->factorCondsWithCommonFields( $nullExpressions );
409 if ( $result !== '' ) {
410 $result .= ' OR ';
411 $wrap = true;
412 }
413 $result .= "($field1 IS NULL AND $factored)";
414 }
415 if ( $wrap ) {
416 return "($result)";
417 } else {
418 return $result;
419 }
420 }
421
425 public function buildConcat( $stringList ) {
426 return 'CONCAT(' . implode( ',', $stringList ) . ')';
427 }
428
430 public function buildGroupConcat( $field, $delim ): string {
431 return 'GROUP_CONCAT(' . $field . ' SEPARATOR ' . $this->quoter->addQuotes( $delim ) . ')';
432 }
433
435 public function limitResult( $sql, $limit, $offset = false ) {
436 if ( !is_numeric( $limit ) ) {
437 throw new DBLanguageError(
438 "Invalid non-numeric limit passed to " . __METHOD__
439 );
440 }
441 // This version works in MySQL and SQLite. It will very likely need to be
442 // overridden for most other RDBMS subclasses.
443 return "$sql LIMIT "
444 . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
445 . "{$limit} ";
446 }
447
453 public function escapeLikeInternal( $s, $escapeChar = '`' ) {
454 return str_replace(
455 [ $escapeChar, '%', '_' ],
456 [ "{$escapeChar}{$escapeChar}", "{$escapeChar}%", "{$escapeChar}_" ],
457 $s
458 );
459 }
460
462 public function buildLike( $param, ...$params ) {
463 if ( is_array( $param ) ) {
464 $params = $param;
465 $param = array_shift( $params );
466 }
467 $likeValue = new LikeValue( $param, ...$params );
468
469 return ' LIKE ' . $likeValue->toSql( $this->quoter );
470 }
471
473 public function anyChar() {
474 return new LikeMatch( '_' );
475 }
476
478 public function anyString() {
479 return new LikeMatch( '%' );
480 }
481
485 public function unionSupportsOrderAndLimit() {
486 return true; // True for almost every DB supported
487 }
488
490 public function unionQueries( $sqls, $all, $options = [] ) {
491 $glue = $all ? ') UNION ALL (' : ') UNION (';
492
493 $sql = '(' . implode( $glue, $sqls ) . ')';
494 if ( !$this->unionSupportsOrderAndLimit() ) {
495 return $sql;
496 }
497 $sql .= $this->makeOrderBy( $options );
498 $limit = $options['LIMIT'] ?? null;
499 $offset = $options['OFFSET'] ?? false;
500 if ( $limit !== null ) {
501 $sql = $this->limitResult( $sql, $limit, $offset );
502 }
503
504 return $sql;
505 }
506
508 public function conditional( $cond, $caseTrueExpression, $caseFalseExpression ) {
509 if ( is_array( $cond ) ) {
510 $cond = $this->makeList( $cond, self::LIST_AND );
511 }
512 if ( $cond instanceof IExpression ) {
513 $cond = $cond->toSql( $this->quoter );
514 }
515 if ( $caseTrueExpression instanceof Subquery ) {
516 $caseTrueExpression = (string)$caseTrueExpression;
517 }
518 if ( $caseFalseExpression instanceof Subquery ) {
519 $caseFalseExpression = (string)$caseFalseExpression;
520 }
521
522 return "(CASE WHEN $cond THEN $caseTrueExpression ELSE $caseFalseExpression END)";
523 }
524
526 public function strreplace( $orig, $old, $new ) {
527 return "REPLACE({$orig}, {$old}, {$new})";
528 }
529
531 public function timestamp( $ts = 0 ) {
532 $t = new ConvertibleTimestamp( $ts );
533 // Let errors bubble up to avoid putting garbage in the DB
534 return $t->getTimestamp( TS::MW );
535 }
536
538 public function timestampOrNull( $ts = null ) {
539 if ( $ts === null ) {
540 return null;
541 } else {
542 return $this->timestamp( $ts );
543 }
544 }
545
547 public function getInfinity() {
548 return 'infinity';
549 }
550
552 public function encodeExpiry( $expiry ) {
553 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
554 ? $this->getInfinity()
555 : $this->timestamp( $expiry );
556 }
557
559 public function decodeExpiry( $expiry, $format = TS::MW ) {
560 if ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) {
561 return 'infinity';
562 }
563
564 return ConvertibleTimestamp::convert( $format, $expiry );
565 }
566
570 public function buildSubstring( $input, $startPosition, $length = null ) {
571 $this->assertBuildSubstringParams( $startPosition, $length );
572 $functionBody = "$input FROM $startPosition";
573 if ( $length !== null ) {
574 $functionBody .= " FOR $length";
575 }
576 return 'SUBSTRING(' . $functionBody . ')';
577 }
578
591 protected function assertBuildSubstringParams( $startPosition, $length ) {
592 if ( $startPosition === 0 ) {
593 // The DBMSs we support use 1-based indexing here.
594 throw new InvalidArgumentException( 'Use 1 as $startPosition for the beginning of the string' );
595 }
596 if ( !is_int( $startPosition ) || $startPosition < 0 ) {
597 throw new InvalidArgumentException(
598 '$startPosition must be a positive integer'
599 );
600 }
601 if ( !( ( is_int( $length ) && $length >= 0 ) || $length === null ) ) {
602 throw new InvalidArgumentException(
603 '$length must be null or an integer greater than or equal to 0'
604 );
605 }
606 }
607
609 public function buildStringCast( $field ) {
610 // In theory this should work for any standards-compliant
611 // SQL implementation, although it may not be the best way to do it.
612 return "CAST( $field AS CHARACTER )";
613 }
614
616 public function buildIntegerCast( $field ) {
617 return 'CAST( ' . $field . ' AS INTEGER )';
618 }
619
621 public function implicitOrderby() {
622 return true;
623 }
624
625 public function setTableAliases( array $aliases ) {
626 $this->tableAliases = $aliases;
627 }
628
632 public function getTableAliases() {
633 return $this->tableAliases;
634 }
635
636 public function setPrefix( string $prefix ) {
637 $this->currentDomain = new DatabaseDomain(
638 $this->currentDomain->getDatabase(),
639 $this->currentDomain->getSchema(),
640 $prefix
641 );
642 }
643
644 public function setCurrentDomain( DatabaseDomain $currentDomain ) {
645 $this->currentDomain = $currentDomain;
646 }
647
652 public function getCurrentDomain() {
653 return $this->currentDomain;
654 }
655
657 public function selectSQLText(
658 $tables, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
659 ) {
660 if ( !is_array( $tables ) ) {
661 if ( $tables === '' || $tables === null || $tables === false ) {
662 $tables = [];
663 } elseif ( is_string( $tables ) ) {
664 $tables = [ $tables ];
665 } else {
666 throw new DBLanguageError( __METHOD__ . ' called with incorrect table parameter' );
667 }
668 }
669
670 if ( is_array( $vars ) ) {
671 $fields = implode( ',', $this->fieldNamesWithAlias( $vars ) );
672 } else {
673 $fields = $vars;
674 }
675
676 $options = (array)$options;
677
678 $useIndexByTable = $options['USE INDEX'] ?? [];
679 if ( !is_array( $useIndexByTable ) ) {
680 if ( count( $tables ) <= 1 ) {
681 $useIndexByTable = [ reset( $tables ) => $useIndexByTable ];
682 } else {
683 $e = new DBLanguageError( __METHOD__ . " got ambiguous USE INDEX ($fname)" );
684 ( $this->errorLogger )( $e );
685 }
686 }
687
688 $ignoreIndexByTable = $options['IGNORE INDEX'] ?? [];
689 if ( !is_array( $ignoreIndexByTable ) ) {
690 if ( count( $tables ) <= 1 ) {
691 $ignoreIndexByTable = [ reset( $tables ) => $ignoreIndexByTable ];
692 } else {
693 $e = new DBLanguageError( __METHOD__ . " got ambiguous IGNORE INDEX ($fname)" );
694 ( $this->errorLogger )( $e );
695 }
696 }
697
698 if (
699 $this->selectOptionsIncludeLocking( $options ) &&
700 $this->selectFieldsOrOptionsAggregate( $vars, $options )
701 ) {
702 // Some DB types (e.g. postgres) disallow FOR UPDATE with aggregate
703 // functions. Discourage use of such queries to encourage compatibility.
704 $this->logger->warning(
705 __METHOD__ . ": aggregation used with a locking SELECT ($fname)"
706 );
707 }
708
709 if ( count( $tables ) ) {
710 $from = ' FROM ' . $this->tableNamesWithIndexClauseOrJOIN(
711 $tables,
712 $useIndexByTable,
713 $ignoreIndexByTable,
714 $join_conds
715 );
716 } else {
717 $from = '';
718 }
719
720 [ $startOpts, $preLimitTail, $postLimitTail ] = $this->makeSelectOptions( $options );
721
722 if ( is_array( $conds ) ) {
723 $where = $this->makeList( $conds, self::LIST_AND );
724 } elseif ( $conds instanceof IExpression ) {
725 $where = $conds->toSql( $this->quoter );
726 } elseif ( $conds === null || $conds === false ) {
727 $where = '';
728 $this->logger->warning(
729 __METHOD__
730 . ' called from '
731 . $fname
732 . ' with incorrect parameters: $conds must be a string or an array',
733 [ 'db_log_category' => 'sql' ]
734 );
735 } elseif ( is_string( $conds ) ) {
736 $where = $conds;
737 } else {
738 throw new DBLanguageError( __METHOD__ . ' called with incorrect parameters' );
739 }
740
741 // Keep historical extra spaces after FROM to avoid testing failures
742 if ( $where === '' || $where === '*' ) {
743 $sql = "SELECT $startOpts $fields $from $preLimitTail";
744 } else {
745 $sql = "SELECT $startOpts $fields $from WHERE $where $preLimitTail";
746 }
747
748 if ( isset( $options['LIMIT'] ) ) {
749 $sql = $this->limitResult( $sql, $options['LIMIT'], $options['OFFSET'] ?? false );
750 }
751 $sql = "$sql $postLimitTail";
752
753 if ( isset( $options['EXPLAIN'] ) ) {
754 $sql = 'EXPLAIN ' . $sql;
755 }
756
757 if (
758 $fname === static::CALLER_UNKNOWN ||
759 str_starts_with( $fname, 'Wikimedia\\Rdbms\\' ) ||
760 $fname === '{closure}'
761 ) {
762 $exception = new RuntimeException();
763
764 // Try to figure out and report the real caller
765 $caller = '';
766 foreach ( $exception->getTrace() as $call ) {
767 if ( str_ends_with( $call['file'] ?? '', 'Test.php' ) ) {
768 // Don't warn when called directly by test code, adding callers there is pointless
769 break;
770 } elseif ( str_starts_with( $call['class'] ?? '', 'Wikimedia\\Rdbms\\' ) ) {
771 // Keep looking for the caller of a rdbms method
772 } elseif ( str_ends_with( $call['class'] ?? '', 'SelectQueryBuilder' ) ) {
773 // Keep looking for the caller of any custom SelectQueryBuilder
774 } else {
775 // Warn about the external caller we found
776 $caller = implode( '::', array_filter( [ $call['class'] ?? null, $call['function'] ] ) );
777 break;
778 }
779 }
780
781 if ( $fname === '{closure}' ) {
782 // Someone did ->caller( __METHOD__ ) in a local function, e.g. in a callback to
783 // getWithSetCallback(), MWCallableUpdate or doAtomicSection(). That's not very helpful.
784 // Provide a more specific message. The caller has to be provided like this:
785 // $method = __METHOD__;
786 // function ( ... ) use ( $method ) { ... }
787 $warning = "SQL query with incorrect caller (__METHOD__ used inside a closure: {caller}): {sql}";
788 } else {
789 $warning = "SQL query did not specify the caller (guessed caller: {caller}): {sql}";
790 }
791
792 $this->logger->warning(
793 $warning,
794 [ 'sql' => $sql, 'caller' => $caller, 'exception' => $exception ]
795 );
796 }
797
798 return $sql;
799 }
800
805 private function selectOptionsIncludeLocking( $options ) {
806 $options = (array)$options;
807 foreach ( [ 'FOR UPDATE', 'LOCK IN SHARE MODE' ] as $lock ) {
808 if ( in_array( $lock, $options, true ) ) {
809 return true;
810 }
811 }
812
813 return false;
814 }
815
821 private function selectFieldsOrOptionsAggregate( $fields, $options ) {
822 foreach ( (array)$options as $key => $value ) {
823 if ( is_string( $key ) ) {
824 if ( preg_match( '/^(?:GROUP BY|HAVING)$/i', $key ) ) {
825 return true;
826 }
827 } elseif ( is_string( $value ) ) {
828 if ( preg_match( '/^(?:DISTINCT|DISTINCTROW)$/i', $value ) ) {
829 return true;
830 }
831 }
832 }
833
834 $regex = '/^(?:COUNT|MIN|MAX|SUM|GROUP_CONCAT|LISTAGG|ARRAY_AGG)\s*\\(/i';
835 foreach ( (array)$fields as $field ) {
836 if ( is_string( $field ) && preg_match( $regex, $field ) ) {
837 return true;
838 }
839 }
840
841 return false;
842 }
843
850 protected function fieldNamesWithAlias( $fields ) {
851 $retval = [];
852 foreach ( $fields as $alias => $field ) {
853 if ( is_numeric( $alias ) ) {
854 $alias = $field;
855 }
856 $retval[] = $this->fieldNameWithAlias( $field, $alias );
857 }
858
859 return $retval;
860 }
861
870 public function fieldNameWithAlias( $name, $alias = false ) {
871 if ( !$alias || (string)$alias === (string)$name ) {
872 return $name;
873 } else {
874 return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
875 }
876 }
877
889 $tables,
890 $use_index = [],
891 $ignore_index = [],
892 $join_conds = []
893 ) {
894 $ret = [];
895 $retJOIN = [];
896 $use_index = (array)$use_index;
897 $ignore_index = (array)$ignore_index;
898 $join_conds = (array)$join_conds;
899
900 foreach ( $tables as $alias => $table ) {
901 if ( !is_string( $alias ) ) {
902 // No alias? Set it equal to the table name
903 $alias = $table;
904 }
905
906 if ( is_array( $table ) ) {
907 // A parenthesized group
908 if ( count( $table ) > 1 ) {
909 $joinedTable = '(' .
910 $this->tableNamesWithIndexClauseOrJOIN(
911 $table, $use_index, $ignore_index, $join_conds ) . ')';
912 } else {
913 // Degenerate case
914 $innerTable = reset( $table );
915 $innerAlias = key( $table );
916 $joinedTable = $this->tableNameWithAlias(
917 $innerTable,
918 is_string( $innerAlias ) ? $innerAlias : $innerTable
919 );
920 }
921 } else {
922 $joinedTable = $this->tableNameWithAlias( $table, $alias );
923 }
924
925 // Is there a JOIN clause for this table?
926 if ( isset( $join_conds[$alias] ) ) {
927 Assert::parameterType( 'array', $join_conds[$alias], "join_conds[$alias]" );
928 [ $joinType, $conds ] = $join_conds[$alias];
929 $tableClause = $this->normalizeJoinType( $joinType );
930 $tableClause .= ' ' . $joinedTable;
931 if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
932 $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
933 if ( $use != '' ) {
934 $tableClause .= ' ' . $use;
935 }
936 }
937 if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
938 $ignore = $this->ignoreIndexClause(
939 implode( ',', (array)$ignore_index[$alias] ) );
940 if ( $ignore != '' ) {
941 $tableClause .= ' ' . $ignore;
942 }
943 }
944 $on = $this->makeList( (array)$conds, self::LIST_AND );
945 if ( $on != '' ) {
946 $tableClause .= ' ON (' . $on . ')';
947 }
948
949 $retJOIN[] = $tableClause;
950 } elseif ( isset( $use_index[$alias] ) ) {
951 // Is there an INDEX clause for this table?
952 $tableClause = $joinedTable;
953 $tableClause .= ' ' . $this->useIndexClause(
954 implode( ',', (array)$use_index[$alias] )
955 );
956
957 $ret[] = $tableClause;
958 } elseif ( isset( $ignore_index[$alias] ) ) {
959 // Is there an INDEX clause for this table?
960 $tableClause = $joinedTable;
961 $tableClause .= ' ' . $this->ignoreIndexClause(
962 implode( ',', (array)$ignore_index[$alias] )
963 );
964
965 $ret[] = $tableClause;
966 } else {
967 $tableClause = $joinedTable;
968
969 $ret[] = $tableClause;
970 }
971 }
972
973 // We can't separate explicit JOIN clauses with ',', use ' ' for those
974 $implicitJoins = implode( ',', $ret );
975 $explicitJoins = implode( ' ', $retJOIN );
976
977 // Compile our final table clause
978 return implode( ' ', [ $implicitJoins, $explicitJoins ] );
979 }
980
989 protected function normalizeJoinType( string $joinType ) {
990 switch ( strtoupper( $joinType ) ) {
991 case 'JOIN':
992 case 'INNER JOIN':
993 return 'JOIN';
994
995 case 'LEFT JOIN':
996 return 'LEFT JOIN';
997
998 case 'STRAIGHT_JOIN':
999 case 'STRAIGHT JOIN':
1000 // MySQL only
1001 return 'JOIN';
1002
1003 default:
1004 return $joinType;
1005 }
1006 }
1007
1019 protected function tableNameWithAlias( $table, $alias = false ) {
1020 if ( is_string( $table ) ) {
1021 $quotedTable = $this->tableName( $table );
1022 } elseif ( $table instanceof Subquery ) {
1023 $quotedTable = (string)$table;
1024 } else {
1025 throw new InvalidArgumentException( "Table must be a string or Subquery" );
1026 }
1027
1028 if ( $alias === false ) {
1029 if ( $table instanceof Subquery ) {
1030 throw new InvalidArgumentException( "Subquery table missing alias" );
1031 }
1032 $quotedTableWithAnyAlias = $quotedTable;
1033 } elseif (
1034 $alias === $table &&
1035 (
1036 str_contains( $alias, '.' ) ||
1037 $this->tableName( $alias, 'raw' ) === $table
1038 )
1039 ) {
1040 $quotedTableWithAnyAlias = $quotedTable;
1041 } else {
1042 $quotedTableWithAnyAlias = $quotedTable . ' ' . $this->addIdentifierQuotes( $alias );
1043 }
1044
1045 return $quotedTableWithAnyAlias;
1046 }
1047
1049 public function tableName( string $name, $format = 'quoted' ) {
1050 $prefix = $this->currentDomain->getTablePrefix();
1051
1052 // Warn about table names that look qualified
1053 if (
1054 (
1055 str_contains( $name, '.' ) &&
1056 !preg_match( '/^information_schema\.[a-z_0-9]+$/', $name )
1057 ) ||
1058 ( $prefix !== '' && str_starts_with( $name, $prefix ) )
1059 ) {
1060 $this->logger->warning(
1061 __METHOD__ . ' called with qualified table ' . $name,
1062 [ 'db_log_category' => 'sql' ]
1063 );
1064 }
1065
1066 // Extract necessary database, schema, table identifiers and quote them as needed
1067 $formattedComponents = [];
1068 foreach ( $this->qualifiedTableComponents( $name ) as $component ) {
1069 if ( $format === 'quoted' ) {
1070 $formattedComponents[] = $this->addIdentifierQuotes( $component );
1071 } else {
1072 $formattedComponents[] = $component;
1073 }
1074 }
1075
1076 return implode( '.', $formattedComponents );
1077 }
1078
1101 public function qualifiedTableComponents( $name ) {
1102 $identifiers = $this->extractTableNameComponents( $name );
1103 if ( count( $identifiers ) > 3 ) {
1104 throw new DBLanguageError( "Too many components in table name '$name'" );
1105 }
1106 // Table alias config and prefixes only apply to unquoted single-identifier names
1107 if ( count( $identifiers ) == 1 && !$this->isQuotedIdentifier( $identifiers[0] ) ) {
1108 [ $table ] = $identifiers;
1109 if ( isset( $this->tableAliases[$table] ) ) {
1110 // This is an "alias" table that uses a different db/schema/prefix scheme
1111 $database = $this->tableAliases[$table]['dbname'];
1112 $schema = is_string( $this->tableAliases[$table]['schema'] )
1113 ? $this->tableAliases[$table]['schema']
1114 : $this->relationSchemaQualifier();
1115 $prefix = is_string( $this->tableAliases[$table]['prefix'] )
1116 ? $this->tableAliases[$table]['prefix']
1117 : $this->currentDomain->getTablePrefix();
1118 } else {
1119 // Use the current database domain to resolve the schema and prefix
1120 $database = '';
1121 $schema = $this->relationSchemaQualifier();
1122 $prefix = $this->currentDomain->getTablePrefix();
1123 }
1124 $qualifierIdentifiers = [ $database, $schema ];
1125 $tableIdentifier = $prefix . $table;
1126 } else {
1127 $qualifierIdentifiers = array_slice( $identifiers, 0, -1 );
1128 $tableIdentifier = end( $identifiers );
1129 }
1130
1131 $components = [];
1132 foreach ( $qualifierIdentifiers as $identifier ) {
1133 if ( $identifier !== null && $identifier !== '' ) {
1134 $components[] = $this->isQuotedIdentifier( $identifier )
1135 ? substr( $identifier, 1, -1 )
1136 : $identifier;
1137 }
1138 }
1139 $components[] = $this->isQuotedIdentifier( $tableIdentifier )
1140 ? substr( $tableIdentifier, 1, -1 )
1141 : $tableIdentifier;
1142
1143 return $components;
1144 }
1145
1152 public function extractTableNameComponents( string $name ) {
1153 $quoteChar = $this->getIdentifierQuoteChar();
1154 $components = [];
1155 foreach ( explode( '.', $name ) as $component ) {
1156 if ( $this->isQuotedIdentifier( $component ) ) {
1157 $unquotedComponent = substr( $component, 1, -1 );
1158 } else {
1159 $unquotedComponent = $component;
1160 }
1161 if ( str_contains( $unquotedComponent, $quoteChar ) ) {
1162 throw new DBLanguageError(
1163 'Table name component contains unexpected quote or dot character' );
1164 }
1165 $components[] = $component;
1166 }
1167 return $components;
1168 }
1169
1195 public function getDatabaseAndTableIdentifier( string $table ) {
1196 $components = $this->qualifiedTableComponents( $table );
1197 switch ( count( $components ) ) {
1198 case 1:
1199 return [ $this->currentDomain->getDatabase() ?? '', $components[0] ];
1200 case 2:
1201 return $components;
1202 default:
1203 throw new DBLanguageError( 'Too many table components' );
1204 }
1205 }
1206
1210 protected function relationSchemaQualifier() {
1211 return $this->currentDomain->getSchema();
1212 }
1213
1215 public function tableNamesN( ...$tables ) {
1216 $retVal = [];
1217
1218 foreach ( $tables as $name ) {
1219 $retVal[] = $this->tableName( $name );
1220 }
1221
1222 return $retVal;
1223 }
1224
1234 public function isQuotedIdentifier( $name ) {
1235 $quoteChar = $this->getIdentifierQuoteChar();
1236 return strlen( $name ) > 1 && $name[0] === $quoteChar && $name[-1] === $quoteChar;
1237 }
1238
1251 public function useIndexClause( $index ) {
1252 return '';
1253 }
1254
1263 public function ignoreIndexClause( $index ) {
1264 return '';
1265 }
1266
1277 protected function makeSelectOptions( array $options ) {
1278 $preLimitTail = $postLimitTail = '';
1279 $startOpts = '';
1280
1281 $noKeyOptions = [];
1282
1283 foreach ( $options as $key => $option ) {
1284 if ( is_numeric( $key ) ) {
1285 $noKeyOptions[$option] = true;
1286 }
1287 }
1288
1289 $preLimitTail .= $this->makeGroupByWithHaving( $options );
1290
1291 $preLimitTail .= $this->makeOrderBy( $options );
1292
1293 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1294 $postLimitTail .= ' FOR UPDATE';
1295 }
1296
1297 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
1298 $postLimitTail .= ' LOCK IN SHARE MODE';
1299 }
1300
1301 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1302 $startOpts .= 'DISTINCT';
1303 }
1304
1305 # Various MySQL extensions
1306 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
1307 $startOpts .= ' /*! STRAIGHT_JOIN */';
1308 }
1309
1310 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
1311 $startOpts .= ' SQL_BIG_RESULT';
1312 }
1313
1314 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
1315 $startOpts .= ' SQL_BUFFER_RESULT';
1316 }
1317
1318 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
1319 $startOpts .= ' SQL_SMALL_RESULT';
1320 }
1321
1322 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
1323 $startOpts .= ' SQL_CALC_FOUND_ROWS';
1324 }
1325
1326 return [ $startOpts, $preLimitTail, $postLimitTail ];
1327 }
1328
1337 protected function makeGroupByWithHaving( $options ) {
1338 $sql = '';
1339 if ( isset( $options['GROUP BY'] ) ) {
1340 $gb = is_array( $options['GROUP BY'] )
1341 ? implode( ',', $options['GROUP BY'] )
1342 : $options['GROUP BY'];
1343 $sql .= ' GROUP BY ' . $gb;
1344 }
1345 if ( isset( $options['HAVING'] ) ) {
1346 if ( $options['HAVING'] instanceof IExpression ) {
1347 $having = $options['HAVING']->toSql( $this->quoter );
1348 } elseif ( is_array( $options['HAVING'] ) ) {
1349 $having = $this->makeList( $options['HAVING'], self::LIST_AND );
1350 } else {
1351 $having = $options['HAVING'];
1352 }
1353
1354 $sql .= ' HAVING ' . $having;
1355 }
1356
1357 return $sql;
1358 }
1359
1368 protected function makeOrderBy( $options ) {
1369 if ( isset( $options['ORDER BY'] ) ) {
1370 $ob = is_array( $options['ORDER BY'] )
1371 ? implode( ',', $options['ORDER BY'] )
1372 : $options['ORDER BY'];
1373
1374 return ' ORDER BY ' . $ob;
1375 }
1376
1377 return '';
1378 }
1379
1381 public function buildGroupConcatField(
1382 $delim, $tables, $field, $conds = '', $join_conds = []
1383 ) {
1384 $fld = $this->buildGroupConcat( $field, $delim );
1385
1386 return '(' . $this->selectSQLText( $tables, $fld, $conds, static::CALLER_SUBQUERY, [], $join_conds ) . ')';
1387 }
1388
1390 public function buildSelectSubquery(
1391 $tables, $vars, $conds = '', $fname = __METHOD__,
1392 $options = [], $join_conds = []
1393 ) {
1394 return new Subquery(
1395 $this->selectSQLText( $tables, $vars, $conds, $fname, $options, $join_conds )
1396 );
1397 }
1398
1404 public function insertSqlText( $table, array $rows ) {
1405 $encTable = $this->tableName( $table );
1406 [ $sqlColumns, $sqlTuples ] = $this->makeInsertLists( $rows );
1407
1408 return [
1409 "INSERT INTO $encTable ($sqlColumns) VALUES $sqlTuples",
1410 "INSERT INTO $encTable ($sqlColumns) VALUES '?'"
1411 ];
1412 }
1413
1426 public function makeInsertLists( array $rows, $aliasPrefix = '', array $typeByColumn = [] ) {
1427 $firstRow = $rows[0];
1428 if ( !is_array( $firstRow ) || !$firstRow ) {
1429 throw new DBLanguageError( 'Got an empty row list or empty row' );
1430 }
1431 // List of columns that define the value tuple ordering
1432 $tupleColumns = array_keys( $firstRow );
1433
1434 $valueTuples = [];
1435 foreach ( $rows as $row ) {
1436 $rowColumns = array_keys( $row );
1437 // VALUES(...) requires a uniform correspondence of (column => value)
1438 if ( $rowColumns !== $tupleColumns ) {
1439 throw new DBLanguageError(
1440 'All rows must specify the same columns in multi-row inserts. Found a row with (' .
1441 implode( ', ', $rowColumns ) . ') ' .
1442 'instead of expected (' . implode( ', ', $tupleColumns ) . ') as in the first row'
1443 );
1444 }
1445 // Make the value tuple that defines this row
1446 $valueTuples[] = '(' . $this->makeList( array_values( $row ), self::LIST_COMMA ) . ')';
1447 }
1448
1449 $magicAliasFields = [];
1450 foreach ( $tupleColumns as $column ) {
1451 $magicAliasFields[] = $aliasPrefix . $column;
1452 }
1453
1454 return [
1455 $this->makeList( $tupleColumns, self::LIST_NAMES ),
1456 implode( ',', $valueTuples ),
1457 $this->makeList( $magicAliasFields, self::LIST_NAMES )
1458 ];
1459 }
1460
1466 public function insertNonConflictingSqlText( $table, array $rows ) {
1467 $encTable = $this->tableName( $table );
1468 [ $sqlColumns, $sqlTuples ] = $this->makeInsertLists( $rows );
1469 [ $sqlVerb, $sqlOpts ] = $this->makeInsertNonConflictingVerbAndOptions();
1470
1471 return [
1472 rtrim( "$sqlVerb $encTable ($sqlColumns) VALUES $sqlTuples $sqlOpts" ),
1473 rtrim( "$sqlVerb $encTable ($sqlColumns) VALUES '?' $sqlOpts" )
1474 ];
1475 }
1476
1482 return [ 'INSERT IGNORE INTO', '' ];
1483 }
1484
1497 $destTable,
1498 $srcTable,
1499 array $varMap,
1500 $conds,
1501 $fname,
1502 array $insertOptions,
1503 array $selectOptions,
1504 $selectJoinConds
1505 ) {
1506 [ $sqlVerb, $sqlOpts ] = $this->isFlagInOptions( 'IGNORE', $insertOptions )
1507 ? $this->makeInsertNonConflictingVerbAndOptions()
1508 : [ 'INSERT INTO', '' ];
1509 $encDstTable = $this->tableName( $destTable );
1510 $sqlDstColumns = implode( ',', array_keys( $varMap ) );
1511 $selectSql = $this->selectSQLText(
1512 $srcTable,
1513 array_values( $varMap ),
1514 $conds,
1515 $fname,
1516 $selectOptions,
1517 $selectJoinConds
1518 );
1519
1520 return rtrim( "$sqlVerb $encDstTable ($sqlDstColumns) $selectSql $sqlOpts" );
1521 }
1522
1529 public function isFlagInOptions( $option, array $options ) {
1530 foreach ( array_keys( $options, $option, true ) as $k ) {
1531 if ( is_int( $k ) ) {
1532 return true;
1533 }
1534 }
1535
1536 return false;
1537 }
1538
1546 public function makeKeyCollisionCondition( array $rows, array $uniqueKey ) {
1547 if ( !$rows ) {
1548 throw new DBLanguageError( "Empty row array" );
1549 } elseif ( !$uniqueKey ) {
1550 throw new DBLanguageError( "Empty unique key array" );
1551 }
1552
1553 if ( count( $uniqueKey ) == 1 ) {
1554 // Use a simple IN(...) clause
1555 $column = reset( $uniqueKey );
1556 $values = array_column( $rows, $column );
1557 if ( count( $values ) !== count( $rows ) ) {
1558 throw new DBLanguageError( "Missing values for unique key ($column)" );
1559 }
1560
1561 return $this->makeList( [ $column => $values ], self::LIST_AND );
1562 }
1563
1564 $nullByUniqueKeyColumn = array_fill_keys( $uniqueKey, null );
1565
1566 $orConds = [];
1567 foreach ( $rows as $row ) {
1568 $rowKeyMap = array_intersect_key( $row, $nullByUniqueKeyColumn );
1569 if ( count( $rowKeyMap ) != count( $uniqueKey ) ) {
1570 throw new DBLanguageError(
1571 "Missing values for unique key (" . implode( ',', $uniqueKey ) . ")"
1572 );
1573 }
1574 $orConds[] = $this->makeList( $rowKeyMap, self::LIST_AND );
1575 }
1576
1577 return count( $orConds ) > 1
1578 ? $this->makeList( $orConds, self::LIST_OR )
1579 : $orConds[0];
1580 }
1581
1590 public function deleteJoinSqlText( $delTable, $joinTable, $delVar, $joinVar, $conds ) {
1591 if ( !$conds ) {
1592 throw new DBLanguageError( __METHOD__ . ' called with empty $conds' );
1593 }
1594
1595 $delTable = $this->tableName( $delTable );
1596 $joinTable = $this->tableName( $joinTable );
1597 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
1598 if ( $conds != '*' ) {
1599 $sql .= 'WHERE ' . $this->makeList( $conds, self::LIST_AND );
1600 }
1601 $sql .= ')';
1602
1603 return $sql;
1604 }
1605
1611 public function deleteSqlText( $table, $conds ) {
1612 $isCondValid = ( is_string( $conds ) || is_array( $conds ) ) && $conds;
1613 if ( !$isCondValid ) {
1614 throw new DBLanguageError( __METHOD__ . ' called with empty conditions' );
1615 }
1616
1617 $encTable = $this->tableName( $table );
1618 $sql = "DELETE FROM $encTable";
1619
1620 $condsSql = '';
1621 $cleanCondsSql = '';
1622 if ( $conds !== self::ALL_ROWS && $conds !== [ self::ALL_ROWS ] ) {
1623 $cleanCondsSql = ' WHERE ' . $this->scrubArray( $conds );
1624 if ( is_array( $conds ) ) {
1625 $conds = $this->makeList( $conds, self::LIST_AND );
1626 }
1627 $condsSql .= ' WHERE ' . $conds;
1628 }
1629 return new Query(
1630 $sql . $condsSql,
1631 self::QUERY_CHANGE_ROWS,
1632 'DELETE',
1633 $table,
1634 $sql . $cleanCondsSql
1635 );
1636 }
1637
1642 private function scrubArray( $array, int $listType = self::LIST_AND ): string {
1643 if ( is_array( $array ) ) {
1644 $scrubbedArray = [];
1645 foreach ( $array as $key => $value ) {
1646 if ( $value instanceof IExpression ) {
1647 $scrubbedArray[$key] = $value->toGeneralizedSql();
1648 } else {
1649 $scrubbedArray[$key] = '?';
1650 }
1651 }
1652 return $this->makeList( $scrubbedArray, $listType );
1653 }
1654 return '?';
1655 }
1656
1664 public function updateSqlText( $table, $set, $conds, $options ) {
1665 $isCondValid = ( is_string( $conds ) || is_array( $conds ) ) && $conds;
1666 if ( !$isCondValid ) {
1667 throw new DBLanguageError( __METHOD__ . ' called with empty conditions' );
1668 }
1669 $encTable = $this->tableName( $table );
1670 $opts = $this->makeUpdateOptions( $options );
1671 $sql = "UPDATE $opts $encTable";
1672 $condsSql = " SET " . $this->makeList( $set, self::LIST_SET );
1673 $cleanCondsSql = " SET " . $this->scrubArray( $set, self::LIST_SET );
1674
1675 if ( $conds !== self::ALL_ROWS && $conds !== [ self::ALL_ROWS ] ) {
1676 $cleanCondsSql .= ' WHERE ' . $this->scrubArray( $conds );
1677 if ( is_array( $conds ) ) {
1678 $conds = $this->makeList( $conds, self::LIST_AND );
1679 }
1680 $condsSql .= ' WHERE ' . $conds;
1681 }
1682 return new Query(
1683 $sql . $condsSql,
1684 self::QUERY_CHANGE_ROWS,
1685 'UPDATE',
1686 $table,
1687 $sql . $cleanCondsSql
1688 );
1689 }
1690
1697 protected function makeUpdateOptions( $options ) {
1698 $opts = $this->makeUpdateOptionsArray( $options );
1699
1700 return implode( ' ', $opts );
1701 }
1702
1709 protected function makeUpdateOptionsArray( $options ) {
1710 $options = $this->normalizeOptions( $options );
1711
1712 $opts = [];
1713
1714 if ( in_array( 'IGNORE', $options ) ) {
1715 $opts[] = 'IGNORE';
1716 }
1717
1718 return $opts;
1719 }
1720
1726 final public function normalizeOptions( $options ) {
1727 if ( is_array( $options ) ) {
1728 return $options;
1729 } elseif ( is_string( $options ) ) {
1730 return ( $options === '' ) ? [] : [ $options ];
1731 } else {
1732 throw new DBLanguageError( __METHOD__ . ': expected string or array' );
1733 }
1734 }
1735
1740 public function dropTableSqlText( $table ) {
1741 // https://mariadb.com/kb/en/drop-table/
1742 // https://dev.mysql.com/doc/refman/8.0/en/drop-table.html
1743 // https://www.postgresql.org/docs/9.2/sql-truncate.html
1744 return "DROP TABLE " . $this->tableName( $table ) . " CASCADE";
1745 }
1746
1752 public function getQueryVerb( $sql ) {
1753 wfDeprecated( __METHOD__, '1.42' );
1754 return QueryBuilderFromRawSql::buildQuery( $sql, 0 )->getVerb();
1755 }
1756
1769 public function isTransactableQuery( Query $sql ) {
1770 return !in_array(
1771 $sql->getVerb(),
1772 [
1773 'BEGIN',
1774 'ROLLBACK',
1775 'ROLLBACK TO SAVEPOINT',
1776 'COMMIT',
1777 'SET',
1778 'SHOW',
1779 'CREATE',
1780 'ALTER',
1781 'USE',
1782 'SHOW'
1783 ],
1784 true
1785 );
1786 }
1787
1792 public function buildExcludedValue( $column ) {
1793 /* @see Database::upsert() */
1794 // This can be treated like a single value since __VALS is a single row table
1795 return "(SELECT __$column FROM __VALS)";
1796 }
1797
1802 public function savepointSqlText( $identifier ) {
1803 return 'SAVEPOINT ' . $this->addIdentifierQuotes( $identifier );
1804 }
1805
1810 public function releaseSavepointSqlText( $identifier ) {
1811 return 'RELEASE SAVEPOINT ' . $this->addIdentifierQuotes( $identifier );
1812 }
1813
1818 public function rollbackToSavepointSqlText( $identifier ) {
1819 return 'ROLLBACK TO SAVEPOINT ' . $this->addIdentifierQuotes( $identifier );
1820 }
1821
1825 public function rollbackSqlText() {
1826 return 'ROLLBACK';
1827 }
1828
1835 public function dispatchingInsertSqlText( $table, $rows, $options ) {
1836 $rows = $this->normalizeRowArray( $rows );
1837 if ( !$rows ) {
1838 return false;
1839 }
1840
1841 $options = $this->normalizeOptions( $options );
1842 if ( $this->isFlagInOptions( 'IGNORE', $options ) ) {
1843 [ $sql, $cleanSql ] = $this->insertNonConflictingSqlText( $table, $rows );
1844 } else {
1845 [ $sql, $cleanSql ] = $this->insertSqlText( $table, $rows );
1846 }
1847 return new Query( $sql, self::QUERY_CHANGE_ROWS, 'INSERT', $table, $cleanSql );
1848 }
1849
1855 final protected function normalizeRowArray( array $rowOrRows ) {
1856 if ( !$rowOrRows ) {
1857 $rows = [];
1858 } elseif ( isset( $rowOrRows[0] ) ) {
1859 $rows = $rowOrRows;
1860 } else {
1861 $rows = [ $rowOrRows ];
1862 }
1863
1864 foreach ( $rows as $row ) {
1865 if ( !is_array( $row ) ) {
1866 throw new DBLanguageError( "Got non-array in row array" );
1867 } elseif ( !$row ) {
1868 throw new DBLanguageError( "Got empty array in row array" );
1869 }
1870 }
1871
1872 return $rows;
1873 }
1874
1883 final public function normalizeUpsertParams( $uniqueKeys, &$rows ) {
1884 $rows = $this->normalizeRowArray( $rows );
1885 if ( !$uniqueKeys ) {
1886 throw new DBLanguageError( 'No unique key specified for upsert/replace' );
1887 }
1888 $uniqueKey = $this->normalizeUpsertKeys( $uniqueKeys );
1889 $this->assertValidUpsertRowArray( $rows, $uniqueKey );
1890
1891 return $uniqueKey;
1892 }
1893
1900 final public function normalizeConditions( $conds, $fname ) {
1901 if ( $conds === null || $conds === false ) {
1902 $this->logger->warning(
1903 __METHOD__
1904 . ' called from '
1905 . $fname
1906 . ' with incorrect parameters: $conds must be a string or an array',
1907 [ 'db_log_category' => 'sql' ]
1908 );
1909 return [];
1910 } elseif ( $conds === '' ) {
1911 return [];
1912 }
1913
1914 return is_array( $conds ) ? $conds : [ $conds ];
1915 }
1916
1922 private function normalizeUpsertKeys( $uniqueKeys ) {
1923 if ( is_string( $uniqueKeys ) ) {
1924 return [ $uniqueKeys ];
1925 } elseif ( !is_array( $uniqueKeys ) ) {
1926 throw new DBLanguageError( 'Invalid unique key array' );
1927 } else {
1928 if ( count( $uniqueKeys ) !== 1 || !isset( $uniqueKeys[0] ) ) {
1929 throw new DBLanguageError(
1930 "The unique key array should contain a single unique index" );
1931 }
1932
1933 $uniqueKey = $uniqueKeys[0];
1934 if ( is_string( $uniqueKey ) ) {
1935 // Passing a list of strings for single-column unique keys is too
1936 // easily confused with passing the columns of composite unique key
1937 $this->logger->warning( __METHOD__ .
1938 " called with deprecated parameter style: " .
1939 "the unique key array should be a string or array of string arrays",
1940 [
1941 'exception' => new RuntimeException(),
1942 'db_log_category' => 'sql',
1943 ] );
1944 return $uniqueKeys;
1945 } elseif ( is_array( $uniqueKey ) ) {
1946 return $uniqueKey;
1947 } else {
1948 throw new DBLanguageError( 'Invalid unique key array entry' );
1949 }
1950 }
1951 }
1952
1958 final protected function assertValidUpsertRowArray( array $rows, array $uniqueKey ) {
1959 foreach ( $rows as $row ) {
1960 foreach ( $uniqueKey as $column ) {
1961 if ( !isset( $row[$column] ) ) {
1962 throw new DBLanguageError(
1963 "NULL/absent values for unique key (" . implode( ',', $uniqueKey ) . ")"
1964 );
1965 }
1966 }
1967 }
1968 }
1969
1976 final public function assertValidUpsertSetArray(
1977 array $set,
1978 array $uniqueKey,
1979 array $rows
1980 ) {
1981 if ( !$set ) {
1982 throw new DBLanguageError( "Update assignment list can't be empty for upsert" );
1983 }
1984
1985 // Sloppy callers might construct the SET array using the ROW array, leaving redundant
1986 // column definitions for unique key columns. Detect this for backwards compatibility.
1987 $soleRow = ( count( $rows ) == 1 ) ? reset( $rows ) : null;
1988 // Disallow value changes for any columns in the unique key. This avoids additional
1989 // insertion order dependencies that are unwieldy and difficult to implement efficiently
1990 // in PostgreSQL.
1991 foreach ( $set as $k => $v ) {
1992 if ( is_string( $k ) ) {
1993 // Key is a column name and value is a literal (e.g. string, int, null, ...)
1994 if ( in_array( $k, $uniqueKey, true ) ) {
1995 if ( $soleRow && array_key_exists( $k, $soleRow ) && $soleRow[$k] === $v ) {
1996 $this->logger->warning(
1997 __METHOD__ . " called with redundant assignment to column '$k'",
1998 [
1999 'exception' => new RuntimeException(),
2000 'db_log_category' => 'sql',
2001 ]
2002 );
2003 } else {
2004 throw new DBLanguageError(
2005 "Cannot reassign column '$k' since it belongs to the provided unique key"
2006 );
2007 }
2008 }
2009 } elseif ( preg_match( '/^([a-zA-Z0-9_]+)\s*=/', $v, $m ) ) {
2010 // Value is of the form "<unquoted alphanumeric column> = <SQL expression>"
2011 if ( in_array( $m[1], $uniqueKey, true ) ) {
2012 throw new DBLanguageError(
2013 "Cannot reassign column '{$m[1]}' since it belongs to the provided unique key"
2014 );
2015 }
2016 }
2017 }
2018 }
2019
2024 final public function extractSingleFieldFromList( $var ) {
2025 if ( is_array( $var ) ) {
2026 if ( !$var ) {
2027 $column = null;
2028 } elseif ( count( $var ) == 1 ) {
2029 $column = $var[0] ?? reset( $var );
2030 } else {
2031 throw new DBLanguageError( __METHOD__ . ': got multiple columns' );
2032 }
2033 } else {
2034 $column = $var;
2035 }
2036
2037 return $column;
2038 }
2039
2041 public function setSchemaVars( $vars ) {
2042 $this->schemaVars = is_array( $vars ) ? $vars : null;
2043 }
2044
2051 protected function getSchemaVars() {
2052 return $this->schemaVars ?? $this->getDefaultSchemaVars();
2053 }
2054
2063 protected function getDefaultSchemaVars() {
2064 return [];
2065 }
2066
2087 public function replaceVars( $ins ) {
2088 $vars = $this->getSchemaVars();
2089 return preg_replace_callback(
2090 '!
2091 /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
2092 \'\{\$ (\w+) }\' | # 3. addQuotes
2093 `\{\$ (\w+) }` | # 4. addIdentifierQuotes
2094 /\*\$ (\w+) \*/ # 5. leave unencoded
2095 !x',
2096 function ( $m ) use ( $vars ) {
2097 // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
2098 // check for both nonexistent keys *and* the empty string.
2099 if ( isset( $m[1] ) && $m[1] !== '' ) {
2100 if ( $m[1] === 'i' ) {
2101 return $m[2];
2102 } else {
2103 return $this->tableName( $m[2] );
2104 }
2105 } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
2106 return $this->quoter->addQuotes( $vars[$m[3]] );
2107 } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
2108 return $this->addIdentifierQuotes( $vars[$m[4]] );
2109 } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
2110 return $vars[$m[5]];
2111 } else {
2112 return $m[0];
2113 }
2114 },
2115 $ins
2116 );
2117 }
2118
2124 public function lockSQLText( $lockName, $timeout ) {
2125 throw new RuntimeException( 'locking must be implemented in subclasses' );
2126 }
2127
2132 public function lockIsFreeSQLText( $lockName ) {
2133 throw new RuntimeException( 'locking must be implemented in subclasses' );
2134 }
2135
2140 public function unlockSQLText( $lockName ) {
2141 throw new RuntimeException( 'locking must be implemented in subclasses' );
2142 }
2143}
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
Class to handle database/schema/prefix specifications for IDatabase.
Used by Database::buildLike() to represent characters that have special meaning in SQL LIKE clauses a...
Definition LikeMatch.php:10
Content of like value.
Definition LikeValue.php:14
buildGroupConcatField( $delim, $tables, $field, $conds='', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.This is useful for combining a field for sev...
getIdentifierQuoteChar()
Get the character used for identifier quoting.
anyChar()
Returns a token for buildLike() that denotes a '_' to be used in a LIKE query.LikeMatch
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.string
insertSelectNativeSqlText( $destTable, $srcTable, array $varMap, $conds, $fname, array $insertOptions, array $selectOptions, $selectJoinConds)
getInfinity()
Find out when 'infinity' is.Most DBMSes support this. This is a special keyword for timestamps in Pos...
buildGreatest( $fields, $values)
Build a GREATEST function statement comparing columns/values.Integer and float values in $values will...
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
buildSelectSubquery( $tables, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Equivalent to IDatabase::selectSQLText() except wraps the result in Subquery.IDatabase::selectSQLText...
makeSelectOptions(array $options)
Returns an optional USE INDEX clause to go after the table, and a string to go at the end of the quer...
strreplace( $orig, $old, $new)
Returns a SQL expression for simple string replacement (e.g.REPLACE() in mysql)string
replaceVars( $ins)
Database-independent variable replacement.
buildStringCast( $field)
string 1.28 in IDatabase, moved to ISQLPlatform in 1.39
normalizeUpsertParams( $uniqueKeys, &$rows)
Validate and normalize parameters to upsert() or replace()
qualifiedTableComponents( $name)
Get the table components needed for a query given the currently selected database/schema.
makeKeyCollisionCondition(array $rows, array $uniqueKey)
Build an SQL condition to find rows with matching key values to those in $rows.
__construct(DbQuoter $quoter, ?LoggerInterface $logger=null, ?DatabaseDomain $currentDomain=null, $errorLogger=null)
array null $schemaVars
Current variables use for schema element placeholders.
ignoreIndexClause( $index)
IGNORE INDEX clause.
makeOrderBy( $options)
Returns an optional ORDER BY.
buildLike( $param,... $params)
LIKE statement wrapper.This takes a variable-length argument list with parts of pattern to match cont...
bitOr( $fieldLeft, $fieldRight)
string
isTransactableQuery(Query $sql)
Determine whether a SQL statement is sensitive to isolation level.
fieldNameWithAlias( $name, $alias=false)
Get an aliased field name e.g.
buildComparison(string $op, array $conds)
Build a condition comparing multiple values, for use with indexes that cover multiple fields,...
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.The SQL should be adjusted so that only the first $limit...
unionSupportsOrderAndLimit()
Determine if the RDBMS supports ORDER BY and LIMIT for separate subqueries within UNION....
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
buildSuperlative( $sqlfunc, $fields, $values)
Build a superlative function statement comparing columns/values.
makeUpdateOptions( $options)
Make UPDATE options for the Database::update function.
assertBuildSubstringParams( $startPosition, $length)
Check type and bounds for parameters to self::buildSubstring()
getDatabaseAndTableIdentifier(string $table)
Get the database identifer and prefixed table name identifier for a table.
assertValidUpsertSetArray(array $set, array $uniqueKey, array $rows)
bitAnd( $fieldLeft, $fieldRight)
string
array[] $tableAliases
Current map of (table => (dbname, schema, prefix) map)
normalizeJoinType(string $joinType)
Validate and normalize a join type.
buildSubstring( $input, $startPosition, $length=null)
unionQueries( $sqls, $all, $options=[])
Construct a UNION query.This is used for providing overload point for other DB abstractions not compa...
buildGroupConcat( $field, $delim)
Build a GROUP_CONCAT expression.string
getDefaultSchemaVars()
Get schema variables to use if none have been set via setSchemaVars().
factorConds( $condsArray)
Given an array of condition arrays representing an OR list of AND lists, for example:(A=1 AND B=2) OR...
dispatchingInsertSqlText( $table, $rows, $options)
updateSqlText( $table, $set, $conds, $options)
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
anyString()
Returns a token for buildLike() that denotes a '' to be used in a LIKE query.LikeMatch
makeWhereFrom2d( $data, $baseKey, $subKey)
Build a "OR" condition with pairs from a two-dimensional array.The associative array should have inte...
tableName(string $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.This does two important things: it quo...
assertValidUpsertRowArray(array $rows, array $uniqueKey)
deleteJoinSqlText( $delTable, $joinTable, $delVar, $joinVar, $conds)
buildLeast( $fields, $values)
Build a LEAST function statement comparing columns/values.Integer and float values in $values will no...
makeInsertLists(array $rows, $aliasPrefix='', array $typeByColumn=[])
Make SQL lists of columns, row tuples, and column aliases for INSERT/VALUES expressions.
useIndexClause( $index)
USE INDEX clause.
encodeExpiry( $expiry)
Encode an expiry time into the DBMS dependent format.string
decodeExpiry( $expiry, $format=TS::MW)
Decode an expiry time into a DBMS independent format.string
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
isFlagInOptions( $option, array $options)
conditional( $cond, $caseTrueExpression, $caseFalseExpression)
Returns an SQL expression for a simple conditional.This doesn't need to be overridden unless CASE isn...
setSchemaVars( $vars)
Set schema variables to be used when streaming commands from SQL files or stdin.Variables appear as S...
setCurrentDomain(DatabaseDomain $currentDomain)
selectSQLText( $tables, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Take the same arguments as IDatabase::select() and return the SQL it would use.This can be useful for...
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
isQuotedIdentifier( $name)
Returns if the given identifier looks quoted or not according to the database convention for quoting ...
getSchemaVars()
Get schema variables.
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.These can be used to make conjunctions or disjunctions...
insertNonConflictingSqlText( $table, array $rows)
addIdentifierQuotes( $s)
Escape a SQL identifier (e.g.table, column, database) for use in a SQL queryDepending on the database...
extractTableNameComponents(string $name)
Extract the dot-separated components of a table name, preserving identifier quotation.
escapeLikeInternal( $s, $escapeChar='`')
fieldNamesWithAlias( $fields)
Gets an array of aliased field names.
buildIntegerCast( $field)
string 1.31 in IDatabase, moved to ISQLPlatform in 1.39
tableNamesN(... $tables)
Fetch a number of table names into a zero-indexed numerical array.Much like tableName(),...
makeUpdateOptionsArray( $options)
Make UPDATE options array for Database::makeUpdateOptions.
tableNameWithAlias( $table, $alias=false)
Get an aliased table name.
callable $errorLogger
Error logging callback.
tableNamesWithIndexClauseOrJOIN( $tables, $use_index=[], $ignore_index=[], $join_conds=[])
Get the aliased table name clause for a FROM clause which might have a JOIN and/or USE INDEX or IGNOR...
This is to contain any regex on SQL work and get rid of them eventually.
Holds information on Query to be executed.
Definition Query.php:17
Raw SQL value to be used in query builders.
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'UseSessionCookieForBotPasswords'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => false, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Interface for query language.