MediaWiki master
LocalFile.php
Go to the documentation of this file.
1<?php
8
9use InvalidArgumentException;
39use RuntimeException;
40use stdClass;
41use UnexpectedValueException;
51use Wikimedia\Timestamp\ConvertibleTimestamp;
52use Wikimedia\Timestamp\TimestampFormat as TS;
53
80class LocalFile extends File {
81 private const VERSION = 13; // cache version
82
83 private const CACHE_FIELD_MAX_LEN = 1000;
84
86 private const MDS_EMPTY = 'empty';
87
89 private const MDS_LEGACY = 'legacy';
90
92 private const MDS_PHP = 'php';
93
95 private const MDS_JSON = 'json';
96
98 private const MAX_PAGE_RENDER_JOBS = 50;
99
101 protected $fileExists;
102
104 private $fileId;
105
107 private $fileTypeId;
108
110 protected $width;
111
113 protected $height;
114
116 protected $bits;
117
119 protected $media_type;
120
122 protected $mime;
123
125 protected $size;
126
128 protected $metadataArray = [];
129
137
139 protected $metadataBlobs = [];
140
148
150 protected $sha1;
151
153 protected $dataLoaded = false;
154
156 protected $extraDataLoaded = false;
157
159 protected $deleted;
160
162 protected $file_id;
163
166
168 protected $repoClass = LocalRepo::class;
169
171 private $historyLine = 0;
172
174 private $historyRes = null;
175
177 private $major_mime;
178
180 private $minor_mime;
181
183 private $timestamp;
184
186 private $user;
187
189 private $description;
190
192 private $descriptionTouched;
193
195 private $upgraded = false;
196
198 private $upgrading;
199
201 private $locked;
202
204 private $lockedOwnTrx;
205
207 private $missing;
208
210 private $metadataStorageHelper;
211
213 private $migrationStage = SCHEMA_COMPAT_OLD;
214
215 // @note: higher than IDBAccessObject constants
216 private const LOAD_ALL = 16; // integer; load all the lazy fields too (like metadata)
217
218 private const ATOMIC_SECTION_LOCK = 'LocalFile::lockingTransaction';
219
232 public static function newFromTitle( $title, $repo, $unused = null ): static {
233 return new static( $title, $repo );
234 }
235
245 public static function newFromRow( $row, $repo ): static {
246 $title = Title::makeTitle( NS_FILE, $row->img_name );
247 $file = new static( $title, $repo );
248 $file->loadFromRow( $row );
249
250 return $file;
251 }
252
264 public static function newFromKey( $sha1, $repo, $timestamp = false ): static|false {
265 $dbr = $repo->getReplicaDB();
266 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
267
268 $queryBuilder->where( [ 'img_sha1' => $sha1 ] );
269
270 if ( $timestamp ) {
271 $queryBuilder->andWhere( [ 'img_timestamp' => $dbr->timestamp( $timestamp ) ] );
272 }
273
274 $row = $queryBuilder->caller( __METHOD__ )->fetchRow();
275 if ( $row ) {
276 return static::newFromRow( $row, $repo );
277 } else {
278 return false;
279 }
280 }
281
302 public static function getQueryInfo( array $options = [] ) {
303 wfDeprecated( __METHOD__, '1.41' );
304 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
305 $queryInfo = FileSelectQueryBuilder::newForFile( $dbr, $options )->getQueryInfo();
306 // needs remapping...
307 return [
308 'tables' => $queryInfo['tables'],
309 'fields' => $queryInfo['fields'],
310 'joins' => $queryInfo['join_conds'],
311 ];
312 }
313
321 public function __construct( $title, $repo ) {
322 parent::__construct( $title, $repo );
323 $this->metadataStorageHelper = new MetadataStorageHelper( $repo );
324 $this->migrationStage = MediaWikiServices::getInstance()->getMainConfig()->get(
325 MainConfigNames::FileSchemaMigrationStage
326 );
327
328 $this->assertRepoDefined();
329 $this->assertTitleDefined();
330 }
331
335 public function getRepo() {
336 return $this->repo;
337 }
338
345 protected function getCacheKey() {
346 return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
347 }
348
352 private function loadFromCache() {
353 $this->dataLoaded = false;
354 $this->extraDataLoaded = false;
355
356 $key = $this->getCacheKey();
357 if ( !$key ) {
358 $this->loadFromDB( IDBAccessObject::READ_NORMAL );
359
360 return;
361 }
362
363 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
364 $cachedValues = $cache->getWithSetCallback(
365 $key,
366 $cache::TTL_WEEK,
367 function ( $oldValue, &$ttl ) use ( $cache ) {
368 $this->loadFromDB( IDBAccessObject::READ_NORMAL );
369
370 $fields = $this->getCacheFields( '' );
371 $cacheVal = [];
372 $cacheVal['fileExists'] = $this->fileExists;
373 if ( $this->fileExists ) {
374 foreach ( $fields as $field ) {
375 $cacheVal[$field] = $this->$field;
376 }
377 }
378 if ( $this->user ) {
379 $cacheVal['user'] = $this->user->getId();
380 $cacheVal['user_text'] = $this->user->getName();
381 }
382
383 // Don't cache metadata items stored as blobs, since they tend to be large
384 if ( $this->metadataBlobs ) {
385 $cacheVal['metadata'] = array_diff_key(
386 $this->metadataArray, $this->metadataBlobs );
387 // Save the blob addresses
388 $cacheVal['metadataBlobs'] = $this->metadataBlobs;
389 } else {
390 $cacheVal['metadata'] = $this->metadataArray;
391 }
392
393 // Strip off excessive entries from the subset of fields that can become large.
394 // If the cache value gets too large and might not fit in the cache,
395 // causing repeat database queries for each access to the file.
396 foreach ( $this->getLazyCacheFields( '' ) as $field ) {
397 if ( isset( $cacheVal[$field] )
398 && strlen( serialize( $cacheVal[$field] ) ) > 100 * 1024
399 ) {
400 unset( $cacheVal[$field] ); // don't let the value get too big
401 if ( $field === 'metadata' ) {
402 unset( $cacheVal['metadataBlobs'] );
403 }
404 }
405 }
406
407 if ( $this->fileExists ) {
408 $ttl = $cache->adaptiveTTL( (int)wfTimestamp( TS::UNIX, $this->timestamp ), $ttl );
409 } else {
410 $ttl = $cache::TTL_DAY;
411 }
412
413 return $cacheVal;
414 },
415 [ 'version' => self::VERSION ]
416 );
417
418 $this->fileExists = $cachedValues['fileExists'];
419 if ( $this->fileExists ) {
420 $this->setProps( $cachedValues );
421 }
422
423 $this->dataLoaded = true;
424 $this->extraDataLoaded = true;
425 foreach ( $this->getLazyCacheFields( '' ) as $field ) {
426 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
427 }
428 }
429
433 public function invalidateCache() {
434 $key = $this->getCacheKey();
435 if ( !$key ) {
436 return;
437 }
438
439 $this->repo->getPrimaryDB()->onTransactionPreCommitOrIdle(
440 static function () use ( $key ) {
441 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
442 },
443 __METHOD__
444 );
445 }
446
454 public function loadFromFile( $path = null ) {
455 $props = $this->repo->getFileProps( $path ?? $this->getVirtualUrl() );
456 $this->setProps( $props );
457 }
458
466 protected function getCacheFields( $prefix = 'img_' ) {
467 if ( $prefix !== '' ) {
468 throw new InvalidArgumentException(
469 __METHOD__ . ' with a non-empty prefix is no longer supported.'
470 );
471 }
472
473 // See self::getQueryInfo() for the fetching of the data from the DB,
474 // self::loadFromRow() for the loading of the object from the DB row,
475 // and self::loadFromCache() for the caching, and self::setProps() for
476 // populating the object from an array of data.
477 return [ 'size', 'width', 'height', 'bits', 'media_type',
478 'major_mime', 'minor_mime', 'timestamp', 'sha1', 'description' ];
479 }
480
488 protected function getLazyCacheFields( $prefix = 'img_' ) {
489 if ( $prefix !== '' ) {
490 throw new InvalidArgumentException(
491 __METHOD__ . ' with a non-empty prefix is no longer supported.'
492 );
493 }
494
495 // Keep this in sync with the omit-lazy option in self::getQueryInfo().
496 return [ 'metadata' ];
497 }
498
504 protected function loadFromDB( $flags = 0 ) {
505 $fname = static::class . '::' . __FUNCTION__;
506
507 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
508 $this->dataLoaded = true;
509 $this->extraDataLoaded = true;
510
511 $dbr = ( $flags & IDBAccessObject::READ_LATEST )
512 ? $this->repo->getPrimaryDB()
513 : $this->repo->getReplicaDB();
514 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
515
516 $queryBuilder->where( [ 'img_name' => $this->getName() ] );
517 $row = $queryBuilder->caller( $fname )->fetchRow();
518
519 if ( $row ) {
520 $this->loadFromRow( $row );
521 } else {
522 $this->fileExists = false;
523 $this->mime = 'unknown/unknown';
524 }
525 }
526
532 protected function loadExtraFromDB() {
533 if ( !$this->title ) {
534 return; // Avoid hard failure when the file does not exist. T221812
535 }
536
537 $fname = static::class . '::' . __FUNCTION__;
538
539 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
540 $this->extraDataLoaded = true;
541
542 $db = $this->repo->getReplicaDB();
543 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
544 if ( !$fieldMap ) {
545 $db = $this->repo->getPrimaryDB();
546 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
547 }
548
549 if ( $fieldMap ) {
550 if ( isset( $fieldMap['metadata'] ) ) {
551 $this->loadMetadataFromDbFieldValue( $db, $fieldMap['metadata'] );
552 }
553 } else {
554 throw new RuntimeException( "Could not find data for image '{$this->getName()}'." );
555 }
556 }
557
563 private function loadExtraFieldsWithTimestamp( IReadableDatabase $dbr, $fname ) {
564 $fieldMap = false;
565
566 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
567 $queryBuilder->where( [ 'img_name' => $this->getName() ] )
568 ->andWhere( [ 'img_timestamp' => $dbr->timestamp( $this->getTimestamp() ) ] );
569 $row = $queryBuilder->caller( $fname )->fetchRow();
570 if ( $row ) {
571 $fieldMap = $this->unprefixRow( $row, 'img_' );
572 } else {
573 // File may have been uploaded over in the meantime; check the old versions
574 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
575 $row = $queryBuilder->where( [ 'oi_name' => $this->getName() ] )
576 ->andWhere( [ 'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() ) ] )
577 ->caller( __METHOD__ )->fetchRow();
578 if ( $row ) {
579 $fieldMap = $this->unprefixRow( $row, 'oi_' );
580 }
581 }
582
583 return $fieldMap;
584 }
585
591 protected function unprefixRow( $row, $prefix = 'img_' ) {
592 $array = (array)$row;
593 $prefixLength = strlen( $prefix );
594
595 // Double check prefix once
596 if ( !str_starts_with( array_key_first( $array ), $prefix ) ) {
597 throw new InvalidArgumentException( __METHOD__ . ': incorrect $prefix parameter' );
598 }
599
600 $decoded = [];
601 foreach ( $array as $name => $value ) {
602 $decoded[substr( $name, $prefixLength )] = $value;
603 }
604
605 return $decoded;
606 }
607
623 public function loadFromRow( $row, $prefix = 'img_' ) {
624 $this->dataLoaded = true;
625
626 $unprefixed = $this->unprefixRow( $row, $prefix );
627
628 $this->name = $unprefixed['name'];
629 $this->media_type = $unprefixed['media_type'];
630
631 $services = MediaWikiServices::getInstance();
632 $this->description = $services->getCommentStore()
633 ->getComment( "{$prefix}description", $row )->text;
634
635 $this->user = $services->getUserFactory()->newFromAnyId(
636 $unprefixed['user'] ?? null,
637 $unprefixed['user_text'] ?? null,
638 $unprefixed['actor'] ?? null
639 );
640
641 $this->timestamp = wfTimestamp( TS::MW, $unprefixed['timestamp'] );
642
643 $this->loadMetadataFromDbFieldValue(
644 $this->repo->getReplicaDB(), $unprefixed['metadata'] );
645
646 if ( empty( $unprefixed['major_mime'] ) ) {
647 $this->major_mime = 'unknown';
648 $this->minor_mime = 'unknown';
649 $this->mime = 'unknown/unknown';
650 } else {
651 if ( !$unprefixed['minor_mime'] ) {
652 $unprefixed['minor_mime'] = 'unknown';
653 }
654 $this->major_mime = $unprefixed['major_mime'];
655 $this->minor_mime = $unprefixed['minor_mime'];
656 $this->mime = $unprefixed['major_mime'] . '/' . $unprefixed['minor_mime'];
657 }
658
659 // Trim zero padding from char/binary field
660 $this->sha1 = rtrim( $unprefixed['sha1'], "\0" );
661
662 // Normalize some fields to integer type, per their database definition.
663 // Use unary + so that overflows will be upgraded to double instead of
664 // being truncated as with intval(). This is important to allow > 2 GiB
665 // files on 32-bit systems.
666 $this->size = +$unprefixed['size'];
667 $this->width = +$unprefixed['width'];
668 $this->height = +$unprefixed['height'];
669 $this->bits = +$unprefixed['bits'];
670
671 // Check for extra fields (deprecated since MW 1.37)
672 $extraFields = array_diff(
673 array_keys( $unprefixed ),
674 [
675 'name', 'media_type', 'description_text', 'description_data',
676 'description_cid', 'user', 'user_text', 'actor', 'timestamp',
677 'metadata', 'major_mime', 'minor_mime', 'sha1', 'size', 'width',
678 'height', 'bits', 'file_id', 'filerevision_id'
679 ]
680 );
681 if ( $extraFields ) {
683 'Passing extra fields (' .
684 implode( ', ', $extraFields )
685 . ') to ' . __METHOD__ . ' was deprecated in MediaWiki 1.37. ' .
686 'Property assignment will be removed in a later version.',
687 '1.37' );
688 foreach ( $extraFields as $field ) {
689 $this->$field = $unprefixed[$field];
690 }
691 }
692
693 $this->fileExists = true;
694 }
695
701 public function load( $flags = 0 ) {
702 if ( !$this->dataLoaded ) {
703 if ( $flags & IDBAccessObject::READ_LATEST ) {
704 $this->loadFromDB( $flags );
705 } else {
706 $this->loadFromCache();
707 }
708 }
709
710 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
711 // @note: loads on name/timestamp to reduce race condition problems
712 $this->loadExtraFromDB();
713 }
714 }
715
720 public function maybeUpgradeRow() {
721 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() || $this->upgrading ) {
722 return;
723 }
724
725 $upgrade = false;
726 $reserialize = false;
727 if ( $this->media_type === null || $this->mime == 'image/svg' ) {
728 $upgrade = true;
729 } else {
730 $handler = $this->getHandler();
731 if ( $handler ) {
732 $validity = $handler->isFileMetadataValid( $this );
733 if ( $validity === MediaHandler::METADATA_BAD ) {
734 $upgrade = true;
735 } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE
736 && $this->repo->isMetadataUpdateEnabled()
737 ) {
738 $upgrade = true;
739 } elseif ( $this->repo->isJsonMetadataEnabled()
740 && $this->repo->isMetadataReserializeEnabled()
741 ) {
742 if ( $this->repo->isSplitMetadataEnabled() && $this->isMetadataOversize() ) {
743 $reserialize = true;
744 } elseif ( $this->metadataSerializationFormat !== self::MDS_EMPTY &&
745 $this->metadataSerializationFormat !== self::MDS_JSON ) {
746 $reserialize = true;
747 }
748 }
749 }
750 }
751
752 if ( $upgrade || $reserialize ) {
753 $this->upgrading = true;
754 // Defer updates unless in auto-commit CLI mode
755 DeferredUpdates::addCallableUpdate( function () use ( $upgrade ) {
756 $this->upgrading = false; // avoid duplicate updates
757 try {
758 if ( $upgrade ) {
759 $this->upgradeRow();
760 } else {
761 $this->reserializeMetadata();
762 }
763 } catch ( LocalFileLockError ) {
764 // let the other process handle it (or do it next time)
765 }
766 } );
767 }
768 }
769
773 public function getUpgraded() {
774 return $this->upgraded;
775 }
776
783 public function getFileIdFromName() {
784 if ( !$this->fileId ) {
785 $dbw = $this->repo->getPrimaryDB();
786 $id = $dbw->newSelectQueryBuilder()
787 ->select( 'file_id' )
788 ->from( 'file' )
789 ->where( [
790 'file_name' => $this->getName(),
791 'file_deleted' => 0
792 ] )
793 ->caller( __METHOD__ )
794 ->fetchField();
795 $this->fileId = $id;
796 }
797
798 return $this->fileId;
799 }
800
807 public function acquireFileIdFromName() {
808 $dbw = $this->repo->getPrimaryDB();
809 $id = $this->getFileIdFromName();
810 if ( $id ) {
811 return $id;
812 }
813 $id = $dbw->newSelectQueryBuilder()
814 ->select( 'file_id' )
815 ->from( 'file' )
816 ->where( [
817 'file_name' => $this->getName(),
818 ] )
819 ->caller( __METHOD__ )
820 ->fetchField();
821 if ( !$id ) {
822 $dbw->newInsertQueryBuilder()
823 ->insertInto( 'file' )
824 ->row( [
825 'file_name' => $this->getName(),
826 // The value will be updated later
827 'file_latest' => 0,
828 'file_deleted' => 0,
829 'file_type' => $this->getFileTypeId(),
830 ] )
831 ->caller( __METHOD__ )->execute();
832 $insertId = $dbw->insertId();
833 if ( !$insertId ) {
834 throw new RuntimeException( 'File entry could not be inserted' );
835 }
836 return $insertId;
837 } else {
838 // Undelete
839 $dbw->newUpdateQueryBuilder()
840 ->update( 'file' )
841 ->set( [ 'file_deleted' => 0 ] )
842 ->where( [ 'file_id' => $id ] )
843 ->caller( __METHOD__ )->execute();
844 return $id;
845 }
846 }
847
848 protected function getFileTypeId(): int {
849 if ( $this->fileTypeId ) {
850 return $this->fileTypeId;
851 }
852 [ $major, $minor ] = self::splitMime( $this->mime );
853 $dbw = $this->repo->getPrimaryDB();
854 $id = $dbw->newSelectQueryBuilder()
855 ->select( 'ft_id' )
856 ->from( 'filetypes' )
857 ->where( [
858 'ft_media_type' => $this->getMediaType(),
859 'ft_major_mime' => $major,
860 'ft_minor_mime' => $minor,
861 ] )
862 ->caller( __METHOD__ )
863 ->fetchField();
864 if ( $id ) {
865 $this->fileTypeId = $id;
866 return $id;
867 }
868 $dbw->newInsertQueryBuilder()
869 ->insertInto( 'filetypes' )
870 ->row( [
871 'ft_media_type' => $this->getMediaType(),
872 'ft_major_mime' => $major,
873 'ft_minor_mime' => $minor,
874 ] )
875 ->caller( __METHOD__ )->execute();
876
877 $id = $dbw->insertId();
878 if ( !$id ) {
879 throw new RuntimeException( 'File entry could not be inserted' );
880 }
881
882 $this->fileTypeId = $id;
883 return $id;
884 }
885
890 public function upgradeRow() {
891 $dbw = $this->repo->getPrimaryDB();
892
893 // Make a DB query condition that will fail to match the image row if the
894 // image was reuploaded while the upgrade was in process.
895 $freshnessCondition = [ 'img_timestamp' => $dbw->timestamp( $this->getTimestamp() ) ];
896
897 $this->loadFromFile();
898
899 # Don't destroy file info of missing files
900 if ( !$this->fileExists ) {
901 wfDebug( __METHOD__ . ": file does not exist, aborting" );
902
903 return;
904 }
905
906 [ $major, $minor ] = self::splitMime( $this->mime );
907
908 wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema" );
909
910 $metadata = $this->getMetadataForDb( $dbw );
911 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
912 $dbw->newUpdateQueryBuilder()
913 ->update( 'image' )
914 ->set( [
915 'img_size' => $this->size,
916 'img_width' => $this->width,
917 'img_height' => $this->height,
918 'img_bits' => $this->bits,
919 'img_media_type' => $this->media_type,
920 'img_major_mime' => $major,
921 'img_minor_mime' => $minor,
922 'img_metadata' => $metadata,
923 'img_sha1' => $this->sha1,
924 ] )
925 ->where( [ 'img_name' => $this->getName() ] )
926 ->andWhere( $freshnessCondition )
927 ->caller( __METHOD__ )->execute();
928 }
929
930 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
931 $dbw->newUpdateQueryBuilder()
932 ->update( 'filerevision' )
933 ->set( [
934 'fr_size' => $this->size,
935 'fr_width' => $this->width,
936 'fr_height' => $this->height,
937 'fr_bits' => $this->bits,
938 'fr_metadata' => $metadata,
939 'fr_sha1' => $this->sha1,
940 ] )
941 ->where( [ 'fr_file' => $this->acquireFileIdFromName() ] )
942 ->andWhere( [ 'fr_timestamp' => $dbw->timestamp( $this->getTimestamp() ) ] )
943 ->caller( __METHOD__ )->execute();
944 }
945
946 $this->invalidateCache();
947
948 $this->upgraded = true; // avoid rework/retries
949 }
950
955 protected function reserializeMetadata() {
956 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
957 return;
958 }
959 $dbw = $this->repo->getPrimaryDB();
960 $metadata = $this->getMetadataForDb( $dbw );
961 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
962 $dbw->newUpdateQueryBuilder()
963 ->update( 'image' )
964 ->set( [ 'img_metadata' => $metadata ] )
965 ->where( [
966 'img_name' => $this->name,
967 'img_timestamp' => $dbw->timestamp( $this->timestamp ),
968 ] )
969 ->caller( __METHOD__ )->execute();
970 }
971 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
972 $dbw->newUpdateQueryBuilder()
973 ->update( 'filerevision' )
974 ->set( [ 'fr_metadata' => $metadata ] )
975 ->where( [ 'fr_file' => $this->acquireFileIdFromName() ] )
976 ->andWhere( [ 'fr_timestamp' => $dbw->timestamp( $this->getTimestamp() ) ] )
977 ->caller( __METHOD__ )->execute();
978 }
979 $this->upgraded = true;
980 }
981
994 public function setProps( $info ) {
995 $this->dataLoaded = true;
996 $fields = $this->getCacheFields( '' );
997 $fields[] = 'fileExists';
998
999 foreach ( $fields as $field ) {
1000 if ( isset( $info[$field] ) ) {
1001 $this->$field = $info[$field];
1002 }
1003 }
1004
1005 // Only our own cache sets these properties, so they both should be present.
1006 if ( isset( $info['user'] ) &&
1007 isset( $info['user_text'] ) &&
1008 $info['user_text'] !== ''
1009 ) {
1010 $this->user = new UserIdentityValue( $info['user'], $info['user_text'] );
1011 }
1012
1013 // Fix up mime fields
1014 if ( isset( $info['major_mime'] ) ) {
1015 $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
1016 } elseif ( isset( $info['mime'] ) ) {
1017 $this->mime = $info['mime'];
1018 [ $this->major_mime, $this->minor_mime ] = self::splitMime( $this->mime );
1019 }
1020
1021 if ( isset( $info['metadata'] ) ) {
1022 if ( is_string( $info['metadata'] ) ) {
1023 $this->loadMetadataFromString( $info['metadata'] );
1024 } elseif ( is_array( $info['metadata'] ) ) {
1025 $this->metadataArray = $info['metadata'];
1026 if ( isset( $info['metadataBlobs'] ) ) {
1027 $this->metadataBlobs = $info['metadataBlobs'];
1028 $this->unloadedMetadataBlobs = array_diff_key(
1029 $this->metadataBlobs,
1030 $this->metadataArray
1031 );
1032 } else {
1033 $this->metadataBlobs = [];
1034 $this->unloadedMetadataBlobs = [];
1035 }
1036 } else {
1037 $logger = LoggerFactory::getInstance( 'LocalFile' );
1038 $logger->warning( __METHOD__ . ' given invalid metadata of type ' .
1039 get_debug_type( $info['metadata'] ) );
1040 $this->metadataArray = [];
1041 }
1042 $this->extraDataLoaded = true;
1043 }
1044 }
1045
1061 public function isMissing() {
1062 if ( $this->missing === null ) {
1063 $fileExists = $this->repo->fileExists( $this->getVirtualUrl() );
1064 $this->missing = !$fileExists;
1065 }
1066
1067 return $this->missing;
1068 }
1069
1077 public function getWidth( $page = 1 ) {
1078 $page = (int)$page;
1079 if ( $page < 1 ) {
1080 $page = 1;
1081 }
1082
1083 $this->load();
1084
1085 if ( $this->isMultipage() ) {
1086 $handler = $this->getHandler();
1087 if ( !$handler ) {
1088 return 0;
1089 }
1090 $dim = $handler->getPageDimensions( $this, $page );
1091 if ( $dim ) {
1092 return $dim['width'];
1093 } else {
1094 // For non-paged media, the false goes through an
1095 // intval, turning failure into 0, so do same here.
1096 return 0;
1097 }
1098 } else {
1099 return $this->width;
1100 }
1101 }
1102
1110 public function getHeight( $page = 1 ) {
1111 $page = (int)$page;
1112 if ( $page < 1 ) {
1113 $page = 1;
1114 }
1115
1116 $this->load();
1117
1118 if ( $this->isMultipage() ) {
1119 $handler = $this->getHandler();
1120 if ( !$handler ) {
1121 return 0;
1122 }
1123 $dim = $handler->getPageDimensions( $this, $page );
1124 if ( $dim ) {
1125 return $dim['height'];
1126 } else {
1127 // For non-paged media, the false goes through an
1128 // intval, turning failure into 0, so do same here.
1129 return 0;
1130 }
1131 } else {
1132 return $this->height;
1133 }
1134 }
1135
1143 public function getDescriptionShortUrl() {
1144 if ( !$this->title ) {
1145 return null; // Avoid hard failure when the file does not exist. T221812
1146 }
1147
1148 $pageId = $this->title->getArticleID();
1149
1150 if ( $pageId ) {
1151 $url = $this->repo->makeUrl( [ 'curid' => $pageId ] );
1152 if ( $url !== false ) {
1153 return $url;
1154 }
1155 }
1156 return null;
1157 }
1158
1165 public function getMetadata() {
1166 wfDeprecated( __METHOD__, '1.37' ); // since 1.47
1167 $data = $this->getMetadataArray();
1168 if ( !$data ) {
1169 return '';
1170 } elseif ( array_keys( $data ) === [ '_error' ] ) {
1171 // Legacy error encoding
1172 return $data['_error'];
1173 } else {
1174 return serialize( $this->getMetadataArray() );
1175 }
1176 }
1177
1184 public function getMetadataArray(): array {
1185 $this->load( self::LOAD_ALL );
1186 if ( $this->unloadedMetadataBlobs ) {
1187 return $this->getMetadataItems(
1188 array_unique( array_merge(
1189 array_keys( $this->metadataArray ),
1190 array_keys( $this->unloadedMetadataBlobs )
1191 ) )
1192 );
1193 }
1194 return $this->metadataArray;
1195 }
1196
1197 public function getMetadataItems( array $itemNames ): array {
1198 $this->load( self::LOAD_ALL );
1199 $result = [];
1200 $addresses = [];
1201 foreach ( $itemNames as $itemName ) {
1202 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
1203 $result[$itemName] = $this->metadataArray[$itemName];
1204 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
1205 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
1206 }
1207 }
1208
1209 if ( $addresses ) {
1210 $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses );
1211 foreach ( $addresses as $itemName => $address ) {
1212 unset( $this->unloadedMetadataBlobs[$itemName] );
1213 $value = $resultFromBlob[$itemName] ?? null;
1214 if ( $value !== null ) {
1215 $result[$itemName] = $value;
1216 $this->metadataArray[$itemName] = $value;
1217 }
1218 }
1219 }
1220 return $result;
1221 }
1222
1234 public function getMetadataForDb( IReadableDatabase $db ) {
1235 $this->load( self::LOAD_ALL );
1236 if ( !$this->metadataArray && !$this->metadataBlobs ) {
1237 $s = '';
1238 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
1239 $s = $this->getJsonMetadata();
1240 } else {
1241 $s = serialize( $this->getMetadataArray() );
1242 }
1243 if ( !is_string( $s ) ) {
1244 throw new RuntimeException( 'Could not serialize image metadata value for DB' );
1245 }
1246 return $db->encodeBlob( $s );
1247 }
1248
1255 private function getJsonMetadata() {
1256 // Directly store data that is not already in BlobStore
1257 $envelope = [
1258 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
1259 ];
1260
1261 // Also store the blob addresses
1262 if ( $this->metadataBlobs ) {
1263 $envelope['blobs'] = $this->metadataBlobs;
1264 }
1265
1266 [ $s, $blobAddresses ] = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope );
1267
1268 // Repeated calls to this function should not keep inserting more blobs
1269 $this->metadataBlobs += $blobAddresses;
1270
1271 return $s;
1272 }
1273
1280 private function isMetadataOversize() {
1281 if ( !$this->repo->isSplitMetadataEnabled() ) {
1282 return false;
1283 }
1284 $threshold = $this->repo->getSplitMetadataThreshold();
1285 $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs );
1286 foreach ( $directItems as $value ) {
1287 if ( strlen( $this->metadataStorageHelper->jsonEncode( $value ) ) > $threshold ) {
1288 return true;
1289 }
1290 }
1291 return false;
1292 }
1293
1302 protected function loadMetadataFromDbFieldValue( IReadableDatabase $db, $metadataBlob ) {
1303 $this->loadMetadataFromString( $db->decodeBlob( $metadataBlob ) );
1304 }
1305
1313 protected function loadMetadataFromString( $metadataString ) {
1314 $this->extraDataLoaded = true;
1315 $this->metadataArray = [];
1316 $this->metadataBlobs = [];
1317 $this->unloadedMetadataBlobs = [];
1318 $metadataString = (string)$metadataString;
1319 if ( $metadataString === '' ) {
1320 $this->metadataSerializationFormat = self::MDS_EMPTY;
1321 return;
1322 }
1323 if ( $metadataString[0] === '{' ) {
1324 $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString );
1325 if ( !$envelope ) {
1326 // Legacy error encoding
1327 $this->metadataArray = [ '_error' => $metadataString ];
1328 $this->metadataSerializationFormat = self::MDS_LEGACY;
1329 } else {
1330 $this->metadataSerializationFormat = self::MDS_JSON;
1331 if ( isset( $envelope['data'] ) ) {
1332 $this->metadataArray = $envelope['data'];
1333 }
1334 if ( isset( $envelope['blobs'] ) ) {
1335 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope['blobs'];
1336 }
1337 }
1338 } else {
1339 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1340 $data = @unserialize( $metadataString );
1341 if ( !is_array( $data ) ) {
1342 // Legacy error encoding
1343 $data = [ '_error' => $metadataString ];
1344 $this->metadataSerializationFormat = self::MDS_LEGACY;
1345 } else {
1346 $this->metadataSerializationFormat = self::MDS_PHP;
1347 }
1348 $this->metadataArray = $data;
1349 }
1350 }
1351
1356 public function getBitDepth() {
1357 $this->load();
1358
1359 return (int)$this->bits;
1360 }
1361
1367 public function getSize() {
1368 $this->load();
1369
1370 return $this->size;
1371 }
1372
1378 public function getMimeType() {
1379 $this->load();
1380
1381 return $this->mime;
1382 }
1383
1390 public function getMediaType() {
1391 $this->load();
1392
1393 return $this->media_type;
1394 }
1395
1407 public function exists() {
1408 $this->load();
1409
1410 return $this->fileExists;
1411 }
1412
1434 protected function getThumbnails( $archiveName = false ) {
1435 if ( $archiveName ) {
1436 $dir = $this->getArchiveThumbPath( $archiveName );
1437 } else {
1438 $dir = $this->getThumbPath();
1439 }
1440
1441 $backend = $this->repo->getBackend();
1442 $files = [ $dir ];
1443 try {
1444 $iterator = $backend->getFileList( [ 'dir' => $dir, 'forWrite' => true ] );
1445 if ( $iterator !== null ) {
1446 foreach ( $iterator as $file ) {
1447 $files[] = $file;
1448 }
1449 }
1450 } catch ( FileBackendError ) {
1451 } // suppress (T56674)
1452
1453 return $files;
1454 }
1455
1464 public function purgeCache( $options = [] ) {
1465 // Refresh metadata in memcached, but don't touch thumbnails or CDN
1466 $this->maybeUpgradeRow();
1467 $this->invalidateCache();
1468
1469 // Delete thumbnails
1470 $this->purgeThumbnails( $options );
1471
1472 // Purge CDN cache for this file
1473 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
1474 $hcu->purgeUrls(
1475 $this->getUrl(),
1476 !empty( $options['forThumbRefresh'] )
1477 ? $hcu::PURGE_PRESEND // just a manual purge
1478 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1479 );
1480 }
1481
1487 public function purgeOldThumbnails( $archiveName ) {
1488 // Get a list of old thumbnails
1489 $thumbs = $this->getThumbnails( $archiveName );
1490
1491 // Delete thumbnails from storage, and prevent the directory itself from being purged
1492 $dir = array_shift( $thumbs );
1493 $this->purgeThumbList( $dir, $thumbs );
1494
1495 $urls = [];
1496 foreach ( $thumbs as $thumb ) {
1497 $urls[] = $this->getArchiveThumbUrl( $archiveName, $thumb );
1498 }
1499
1500 // Purge any custom thumbnail caches
1501 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, $archiveName, $urls );
1502
1503 // Purge the CDN
1504 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
1505 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1506 }
1507
1514 public function purgeThumbnails( $options = [] ) {
1515 $thumbs = $this->getThumbnails();
1516
1517 // Delete thumbnails from storage, and prevent the directory itself from being purged
1518 $dir = array_shift( $thumbs );
1519 $this->purgeThumbList( $dir, $thumbs );
1520
1521 // Always purge all files from CDN regardless of handler filters
1522 $urls = [];
1523 foreach ( $thumbs as $thumb ) {
1524 $urls[] = $this->getThumbUrl( $thumb );
1525 }
1526
1527 // Give the media handler a chance to filter the file purge list
1528 if ( !empty( $options['forThumbRefresh'] ) ) {
1529 $handler = $this->getHandler();
1530 if ( $handler ) {
1531 $handler->filterThumbnailPurgeList( $thumbs, $options );
1532 }
1533 }
1534
1535 // Purge any custom thumbnail caches
1536 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, false, $urls );
1537
1538 // Purge the CDN
1539 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
1540 $hcu->purgeUrls(
1541 $urls,
1542 !empty( $options['forThumbRefresh'] )
1543 ? $hcu::PURGE_PRESEND // just a manual purge
1544 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1545 );
1546 }
1547
1554 public function prerenderThumbnails() {
1555 $uploadThumbnailRenderMap = MediaWikiServices::getInstance()
1556 ->getMainConfig()->get( MainConfigNames::UploadThumbnailRenderMap );
1557
1558 $jobs = [];
1559
1560 $sizes = $uploadThumbnailRenderMap;
1561 rsort( $sizes );
1562
1563 foreach ( $sizes as $size ) {
1564 if ( $this->isMultipage() ) {
1565 // (T309114) Only trigger render jobs up to MAX_PAGE_RENDER_JOBS to avoid
1566 // a flood of jobs for huge files.
1567 $pageLimit = min( $this->pageCount(), self::MAX_PAGE_RENDER_JOBS );
1568
1569 $jobs[] = new ThumbnailRenderJob(
1570 $this->getTitle(),
1571 [
1572 'transformParams' => [ 'width' => $size, 'page' => 1 ],
1573 'enqueueNextPage' => true,
1574 'pageLimit' => $pageLimit
1575 ]
1576 );
1577 } elseif ( $this->isVectorized() || $this->getWidth() > $size ) {
1578 $jobs[] = new ThumbnailRenderJob(
1579 $this->getTitle(),
1580 [ 'transformParams' => [ 'width' => $size ] ]
1581 );
1582 }
1583 }
1584
1585 if ( $jobs ) {
1586 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $jobs );
1587 }
1588 }
1589
1596 protected function purgeThumbList( $dir, $files ) {
1597 $fileListDebug = strtr(
1598 var_export( $files, true ),
1599 [ "\n" => '' ]
1600 );
1601 wfDebug( __METHOD__ . ": $fileListDebug" );
1602
1603 if ( $this->repo->supportsSha1URLs() ) {
1604 $reference = $this->getSha1();
1605 } else {
1606 $reference = $this->getName();
1607 }
1608
1609 $purgeList = [];
1610 foreach ( $files as $file ) {
1611 # Check that the reference (filename or sha1) is part of the thumb name
1612 # This is a basic check to avoid erasing unrelated directories
1613 if ( str_contains( $file, $reference )
1614 || str_contains( $file, "-thumbnail" ) // "short" thumb name
1615 ) {
1616 $purgeList[] = "{$dir}/{$file}";
1617 }
1618 }
1619
1620 # Delete the thumbnails
1621 $this->repo->quickPurgeBatch( $purgeList );
1622 # Clear out the thumbnail directory if empty
1623 $this->repo->quickCleanDir( $dir );
1624 }
1625
1637 public function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1638 if ( !$this->exists() ) {
1639 return []; // Avoid hard failure when the file does not exist. T221812
1640 }
1641
1642 $dbr = $this->repo->getReplicaDB();
1643 $oldFileQuery = FileSelectQueryBuilder::newForOldFile( $dbr )->getQueryInfo();
1644
1645 $tables = $oldFileQuery['tables'];
1646 $fields = $oldFileQuery['fields'];
1647 $join_conds = $oldFileQuery['join_conds'];
1648 $conds = $opts = [];
1649 $eq = $inc ? '=' : '';
1650 $conds[] = $dbr->expr( 'oi_name', '=', $this->title->getDBkey() );
1651
1652 if ( $start ) {
1653 $conds[] = $dbr->expr( 'oi_timestamp', "<$eq", $dbr->timestamp( $start ) );
1654 }
1655
1656 if ( $end ) {
1657 $conds[] = $dbr->expr( 'oi_timestamp', ">$eq", $dbr->timestamp( $end ) );
1658 }
1659
1660 if ( $limit ) {
1661 $opts['LIMIT'] = $limit;
1662 }
1663
1664 // Search backwards for time > x queries
1665 $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1666 $opts['ORDER BY'] = "oi_timestamp $order";
1667 $opts['USE INDEX'] = [ 'oldimage' => 'oi_name_timestamp' ];
1668
1669 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1670 $conds, $opts, $join_conds );
1671
1672 $res = $dbr->newSelectQueryBuilder()
1673 ->tables( $tables )
1674 ->fields( $fields )
1675 ->conds( $conds )
1676 ->caller( __METHOD__ )
1677 ->options( $opts )
1678 ->joinConds( $join_conds )
1679 ->fetchResultSet();
1680 $r = [];
1681
1682 foreach ( $res as $row ) {
1683 $r[] = $this->repo->newFileFromRow( $row );
1684 }
1685
1686 if ( $order == 'ASC' ) {
1687 $r = array_reverse( $r ); // make sure it ends up descending
1688 }
1689
1690 return $r;
1691 }
1692
1703 public function nextHistoryLine() {
1704 if ( !$this->exists() ) {
1705 return false; // Avoid hard failure when the file does not exist. T221812
1706 }
1707
1708 # Polymorphic function name to distinguish foreign and local fetches
1709 $fname = static::class . '::' . __FUNCTION__;
1710
1711 $dbr = $this->repo->getReplicaDB();
1712
1713 if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1714 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
1715
1716 $queryBuilder->fields( [ 'oi_archive_name' => $dbr->addQuotes( '' ), 'oi_deleted' => '0' ] )
1717 ->where( [ 'img_name' => $this->title->getDBkey() ] );
1718 $this->historyRes = $queryBuilder->caller( $fname )->fetchResultSet();
1719
1720 if ( $this->historyRes->numRows() == 0 ) {
1721 $this->historyRes = null;
1722
1723 return false;
1724 }
1725 } elseif ( $this->historyLine == 1 ) {
1726 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
1727
1728 $this->historyRes = $queryBuilder->where( [ 'oi_name' => $this->title->getDBkey() ] )
1729 ->orderBy( 'oi_timestamp', SelectQueryBuilder::SORT_DESC )
1730 ->caller( $fname )->fetchResultSet();
1731 }
1732 $this->historyLine++;
1733
1734 return $this->historyRes->fetchObject();
1735 }
1736
1741 public function resetHistory() {
1742 $this->historyLine = 0;
1743
1744 if ( $this->historyRes !== null ) {
1745 $this->historyRes = null;
1746 }
1747 }
1748
1782 public function upload( $src, $comment, $pageText, $flags = 0, $props = false,
1783 $timestamp = false, ?Authority $uploader = null, $tags = [],
1784 $createDummyRevision = true, $revert = false
1785 ) {
1786 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1787 return $this->readOnlyFatalStatus();
1788 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1789 // Check this in advance to avoid writing to FileBackend and the file tables,
1790 // only to fail on insert the revision due to the text store being unavailable.
1791 return $this->readOnlyFatalStatus();
1792 }
1793
1794 $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1795 if ( !$props ) {
1796 if ( FileRepo::isVirtualUrl( $srcPath )
1797 || FileBackend::isStoragePath( $srcPath )
1798 ) {
1799 $props = $this->repo->getFileProps( $srcPath );
1800 } else {
1801 $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1802 $props = $mwProps->getPropsFromPath( $srcPath, true );
1803 }
1804 }
1805
1806 $options = [];
1807 $handler = MediaHandler::getHandler( $props['mime'] );
1808 if ( $handler ) {
1809 if ( is_string( $props['metadata'] ) ) {
1810 // This supports callers directly fabricating a metadata
1811 // property using serialize(). Normally the metadata property
1812 // comes from MWFileProps, in which case it won't be a string.
1813 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1814 $metadata = @unserialize( $props['metadata'] );
1815 } else {
1816 $metadata = $props['metadata'];
1817 }
1818
1819 if ( is_array( $metadata ) ) {
1820 $options['headers'] = $handler->getContentHeaders( $metadata );
1821 }
1822 } else {
1823 $options['headers'] = [];
1824 }
1825
1826 // Trim spaces on user supplied text
1827 $comment = trim( $comment );
1828
1829 $status = $this->publish( $src, $flags, $options );
1830
1831 if ( $status->successCount >= 2 ) {
1832 // There will be a copy+(one of move,copy,store).
1833 // The first succeeding does not commit us to updating the DB
1834 // since it simply copied the current version to a timestamped file name.
1835 // It is only *preferable* to avoid leaving such files orphaned.
1836 // Once the second operation goes through, then the current version was
1837 // updated and we must therefore update the DB too.
1838 $oldver = $status->value;
1839
1840 $uploadStatus = $this->recordUpload3(
1841 $oldver,
1842 $comment,
1843 $pageText,
1844 $uploader ?? RequestContext::getMain()->getAuthority(),
1845 $props,
1846 $timestamp,
1847 $tags,
1848 $createDummyRevision,
1849 $revert
1850 );
1851 if ( !$uploadStatus->isOK() ) {
1852 if ( $uploadStatus->hasMessage( 'filenotfound' ) ) {
1853 // update filenotfound error with more specific path
1854 $status->fatal( 'filenotfound', $srcPath );
1855 } else {
1856 $status->merge( $uploadStatus );
1857 }
1858 }
1859 }
1860
1861 return $status;
1862 }
1863
1880 public function recordUpload3(
1881 string $oldver,
1882 string $comment,
1883 string $pageText,
1884 Authority $performer,
1885 $props = false,
1886 $timestamp = false,
1887 $tags = [],
1888 bool $createDummyRevision = true,
1889 bool $revert = false
1890 ): Status {
1891 $dbw = $this->repo->getPrimaryDB();
1892
1893 # Imports or such might force a certain timestamp; otherwise we generate
1894 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1895 if ( $timestamp === false ) {
1896 $timestamp = $dbw->timestamp();
1897 $allowTimeKludge = true;
1898 } else {
1899 $allowTimeKludge = false;
1900 }
1901
1902 $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1903 $props['description'] = $comment;
1904 $props['timestamp'] = wfTimestamp( TS::MW, $timestamp ); // DB -> TS::MW
1905 $this->setProps( $props );
1906
1907 # Fail now if the file isn't there
1908 if ( !$this->fileExists ) {
1909 wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!" );
1910
1911 return Status::newFatal( 'filenotfound', $this->getRel() );
1912 }
1913
1914 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
1915 if ( !$mimeAnalyzer->isValidMajorMimeType( $this->major_mime ) ) {
1916 $this->major_mime = 'unknown';
1917 }
1918
1919 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1920
1921 // T391473: File uploads can involve moving a lot of bytes around. Sometimes in
1922 // that time the DB connection can timeout. Normally this is automatically
1923 // reconnected, but reconnection does not work inside atomic sections.
1924 // Ping the DB to ensure it is still there prior to entering the atomic
1925 // section. TODO: Refactor upload jobs to be smarter about implicit transactions.
1926 $dbw->ping();
1927 $dbw->startAtomic( __METHOD__ );
1928
1929 $actorId = $actorNormalizaton->acquireActorId( $performer->getUser(), $dbw );
1930 $this->user = $performer->getUser();
1931 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1932
1933 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
1934 // Test to see if the row exists using INSERT IGNORE
1935 // This avoids race conditions by locking the row until the commit, and also
1936 // doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1937 $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
1938 $actorFields = [ 'img_actor' => $actorId ];
1939 $dbw->newInsertQueryBuilder()
1940 ->insertInto( 'image' )
1941 ->ignore()
1942 ->row( [
1943 'img_name' => $this->getName(),
1944 'img_size' => $this->size,
1945 'img_width' => intval( $this->width ),
1946 'img_height' => intval( $this->height ),
1947 'img_bits' => $this->bits,
1948 'img_media_type' => $this->media_type,
1949 'img_major_mime' => $this->major_mime,
1950 'img_minor_mime' => $this->minor_mime,
1951 'img_timestamp' => $dbw->timestamp( $timestamp ),
1952 'img_metadata' => $this->getMetadataForDb( $dbw ),
1953 'img_sha1' => $this->sha1
1954 ] + $commentFields + $actorFields )
1955 ->caller( __METHOD__ )->execute();
1956 $reupload = ( $dbw->affectedRows() == 0 );
1957 } else {
1958 $reupload = $this->getFileIdFromName();
1959 }
1960
1961 if ( $reupload ) {
1962 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
1963 $row = $dbw->newSelectQueryBuilder()
1964 ->select( [ 'img_timestamp', 'img_sha1' ] )
1965 ->from( 'image' )
1966 ->where( [ 'img_name' => $this->getName() ] )
1967 ->caller( __METHOD__ )->fetchRow();
1968 } else {
1969 $row = $dbw->newSelectQueryBuilder()
1970 ->select( [
1971 'img_timestamp' => 'fr_timestamp',
1972 'img_sha1' => 'fr_sha1',
1973 ] )
1974 ->from( 'file' )
1975 ->join( 'filerevision', null, 'file_latest = fr_id' )
1976 ->where( [ 'file_id' => $this->getFileIdFromName() ] )
1977 ->caller( __METHOD__ )->fetchRow();
1978 }
1979
1980 if ( $row && $row->img_sha1 === $this->sha1 ) {
1981 $dbw->endAtomic( __METHOD__ );
1982 wfDebug( __METHOD__ . ": File " . $this->getRel() . " already exists!" );
1983 $title = Title::newFromText( $this->getName(), NS_FILE );
1984 return Status::newFatal( 'fileexists-no-change', $title->getPrefixedText() );
1985 }
1986 if ( $allowTimeKludge ) {
1987 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
1988 $lUnixtime = $row ? (int)wfTimestamp( TS::UNIX, $row->img_timestamp ) : false;
1989 # Avoid a timestamp that is not newer than the last version
1990 # TODO: the image/oldimage tables should be like page/revision with an ID field
1991 if ( $lUnixtime && (int)wfTimestamp( TS::UNIX, $timestamp ) <= $lUnixtime ) {
1992 sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
1993 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
1994 $this->timestamp = wfTimestamp( TS::MW, $timestamp ); // DB -> TS::MW
1995 }
1996 }
1997 }
1998 $latestFileRevId = null;
1999 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
2000 if ( $reupload ) {
2001 $latestFileRevId = $dbw->newSelectQueryBuilder()
2002 ->select( 'fr_id' )
2003 ->from( 'filerevision' )
2004 ->where( [ 'fr_file' => $this->acquireFileIdFromName() ] )
2005 ->orderBy( 'fr_timestamp', 'DESC' )
2006 ->caller( __METHOD__ )
2007 ->fetchField();
2008 }
2009 $commentFieldsNew = $commentStore->insert( $dbw, 'fr_description', $comment );
2010 $dbw->newInsertQueryBuilder()
2011 ->insertInto( 'filerevision' )
2012 ->row( [
2013 'fr_file' => $this->acquireFileIdFromName(),
2014 'fr_size' => $this->size,
2015 'fr_width' => intval( $this->width ),
2016 'fr_height' => intval( $this->height ),
2017 'fr_bits' => $this->bits,
2018 'fr_actor' => $actorId,
2019 'fr_deleted' => 0,
2020 'fr_timestamp' => $dbw->timestamp( $timestamp ),
2021 'fr_metadata' => $this->getMetadataForDb( $dbw ),
2022 'fr_sha1' => $this->sha1
2023 ] + $commentFieldsNew )
2024 ->caller( __METHOD__ )->execute();
2025 $dbw->newUpdateQueryBuilder()
2026 ->update( 'file' )
2027 ->set( [ 'file_latest' => $dbw->insertId() ] )
2028 ->where( [ 'file_id' => $this->getFileIdFromName() ] )
2029 ->caller( __METHOD__ )->execute();
2030 }
2031
2032 if ( $reupload ) {
2033 if ( ( $this->migrationStage & SCHEMA_COMPAT_WRITE_NEW ) && $latestFileRevId && $oldver ) {
2034 $dbw->newUpdateQueryBuilder()
2035 ->update( 'filerevision' )
2036 ->set( [ 'fr_archive_name' => $oldver ] )
2037 ->where( [ 'fr_id' => $latestFileRevId ] )
2038 ->caller( __METHOD__ )->execute();
2039 }
2040
2041 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
2042 $tables = [ 'image' ];
2043 $fields = [
2044 'oi_name' => 'img_name',
2045 'oi_archive_name' => $dbw->addQuotes( $oldver ),
2046 'oi_size' => 'img_size',
2047 'oi_width' => 'img_width',
2048 'oi_height' => 'img_height',
2049 'oi_bits' => 'img_bits',
2050 'oi_description_id' => 'img_description_id',
2051 'oi_timestamp' => 'img_timestamp',
2052 'oi_metadata' => 'img_metadata',
2053 'oi_media_type' => 'img_media_type',
2054 'oi_major_mime' => 'img_major_mime',
2055 'oi_minor_mime' => 'img_minor_mime',
2056 'oi_sha1' => 'img_sha1',
2057 'oi_actor' => 'img_actor',
2058 ];
2059 $joins = [];
2060 // (T36993) Note: $oldver can be empty here, if the previous
2061 // version of the file was broken. Allow registration of the new
2062 // version to continue anyway, because that's better than having
2063 // an image that's not fixable by user operations.
2064 // Collision, this is an update of a file
2065 // Insert previous contents into oldimage
2066 $dbw->insertSelect( 'oldimage', $tables, $fields,
2067 [ 'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
2068
2069 // Update the current image row
2070 $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
2071 $actorFields = [ 'img_actor' => $actorId ];
2072 $dbw->newUpdateQueryBuilder()
2073 ->update( 'image' )
2074 ->set( [
2075 'img_size' => $this->size,
2076 'img_width' => intval( $this->width ),
2077 'img_height' => intval( $this->height ),
2078 'img_bits' => $this->bits,
2079 'img_media_type' => $this->media_type,
2080 'img_major_mime' => $this->major_mime,
2081 'img_minor_mime' => $this->minor_mime,
2082 'img_timestamp' => $dbw->timestamp( $timestamp ),
2083 'img_metadata' => $this->getMetadataForDb( $dbw ),
2084 'img_sha1' => $this->sha1
2085 ] + $commentFields + $actorFields )
2086 ->where( [ 'img_name' => $this->getName() ] )
2087 ->caller( __METHOD__ )->execute();
2088 }
2089 }
2090
2091 $descTitle = $this->getTitle();
2092 $descId = $descTitle->getArticleID();
2093 $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $descTitle );
2094 if ( !$wikiPage instanceof WikiFilePage ) {
2095 throw new UnexpectedValueException( 'Cannot obtain instance of WikiFilePage for ' . $this->getName()
2096 . ', got instance of ' . get_class( $wikiPage ) );
2097 }
2098 $wikiPage->setFile( $this );
2099
2100 // Determine log action. If reupload is done by reverting, use a special log_action.
2101 if ( $revert ) {
2102 $logAction = 'revert';
2103 } elseif ( $reupload ) {
2104 $logAction = 'overwrite';
2105 } else {
2106 $logAction = 'upload';
2107 }
2108 // Add the log entry...
2109 $logEntry = new ManualLogEntry( 'upload', $logAction );
2110 $logEntry->setTimestamp( $this->timestamp );
2111 $logEntry->setPerformer( $performer->getUser() );
2112 $logEntry->setComment( $comment );
2113 $logEntry->setTarget( $descTitle );
2114 // Allow people using the api to associate log entries with the upload.
2115 // Log has a timestamp, but sometimes different from upload timestamp.
2116 $logEntry->setParameters(
2117 [
2118 'img_sha1' => $this->sha1,
2119 'img_timestamp' => $timestamp,
2120 ]
2121 );
2122 // Note we keep $logId around since during new image
2123 // creation, page doesn't exist yet, so log_page = 0
2124 // but we want it to point to the page we're making,
2125 // so we later modify the log entry.
2126 // For a similar reason, we avoid making an RC entry
2127 // now and wait until the page exists.
2128 $logId = $logEntry->insert();
2129
2130 if ( $descTitle->exists() ) {
2131 if ( $createDummyRevision ) {
2132 $services = MediaWikiServices::getInstance();
2133 // Use own context to get the action text in content language
2134 $formatter = $services->getLogFormatterFactory()->newFromEntry( $logEntry );
2135 $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
2136 $editSummary = $formatter->getPlainActionText();
2137
2138 $dummyRevRecord = $wikiPage->newPageUpdater( $performer->getUser() )
2139 ->setCause( PageUpdater::CAUSE_UPLOAD )
2140 ->saveDummyRevision( $editSummary, EDIT_SILENT );
2141
2142 // Associate dummy revision id
2143 $logEntry->setAssociatedRevId( $dummyRevRecord->getId() );
2144 }
2145
2146 $newPageContent = null;
2147 } else {
2148 // Make the description page and RC log entry post-commit
2149 $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
2150 }
2151
2152 // NOTE: Even after ending this atomic section, we are probably still in the implicit
2153 // transaction started by any prior master query in the request. We cannot yet safely
2154 // schedule jobs, see T263301.
2155 $dbw->endAtomic( __METHOD__ );
2156 $fname = __METHOD__;
2157
2158 # Do some cache purges after final commit so that:
2159 # a) Changes are more likely to be seen post-purge
2160 # b) They won't cause rollback of the log publish/update above
2161 $purgeUpdate = new AutoCommitUpdate(
2162 $dbw,
2163 __METHOD__,
2164 function () use (
2165 $reupload, $wikiPage, $newPageContent, $comment, $performer,
2166 $logEntry, $logId, $descId, $tags, $fname
2167 ) {
2168 # Update memcache after the commit
2169 $this->invalidateCache();
2170
2171 $updateLogPage = false;
2172 if ( $newPageContent ) {
2173 # New file page; create the description page.
2174 # There's already a log entry, so don't make a second RC entry
2175 # CDN and file cache for the description page are purged by doUserEditContent.
2176 $revRecord = $wikiPage->newPageUpdater( $performer )
2177 ->setCause( PageUpdater::CAUSE_UPLOAD )
2178 ->setContent( SlotRecord::MAIN, $newPageContent )
2179 ->saveRevision( $comment, EDIT_NEW | EDIT_SUPPRESS_RC );
2180
2181 if ( $revRecord ) {
2182 // Associate new page revision id
2183 $logEntry->setAssociatedRevId( $revRecord->getId() );
2184
2185 // This relies on the resetArticleID() call in WikiPage::insertOn(),
2186 // which is triggered on $descTitle by doUserEditContent() above.
2187 $updateLogPage = $revRecord->getPageId();
2188 }
2189 } else {
2190 # Existing file page: invalidate description page cache
2191 $title = $wikiPage->getTitle();
2192 $title->invalidateCache();
2193 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2194 $hcu->purgeTitleUrls( $title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2195 # Allow the new file version to be patrolled from the page footer
2196 Article::purgePatrolFooterCache( $descId );
2197 }
2198
2199 # Update associated rev id. This should be done by $logEntry->insert() earlier,
2200 # but setAssociatedRevId() wasn't called at that point yet...
2201 $logParams = $logEntry->getParameters();
2202 $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
2203 $update = [ 'log_params' => LogEntryBase::makeParamBlob( $logParams ) ];
2204 if ( $updateLogPage ) {
2205 # Also log page, in case where we just created it above
2206 $update['log_page'] = $updateLogPage;
2207 }
2208 $this->getRepo()->getPrimaryDB()->newUpdateQueryBuilder()
2209 ->update( 'logging' )
2210 ->set( $update )
2211 ->where( [ 'log_id' => $logId ] )
2212 ->caller( $fname )->execute();
2213
2214 $this->getRepo()->getPrimaryDB()->newInsertQueryBuilder()
2215 ->insertInto( 'log_search' )
2216 ->row( [
2217 'ls_field' => 'associated_rev_id',
2218 'ls_value' => (string)$logEntry->getAssociatedRevId(),
2219 'ls_log_id' => $logId,
2220 ] )
2221 ->caller( $fname )->execute();
2222
2223 # Add change tags, if any
2224 if ( $tags ) {
2225 $logEntry->addTags( $tags );
2226 }
2227
2228 # Uploads can be patrolled
2229 $logEntry->setIsPatrollable( true );
2230
2231 # Now that the log entry is up-to-date, make an RC entry.
2232 $logEntry->publish( $logId );
2233
2234 # Run hook for other updates (typically more cache purging)
2235 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
2236
2237 if ( $reupload ) {
2238 # Delete old thumbnails
2239 $this->purgeThumbnails();
2240 # Remove the old file from the CDN cache
2241 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2242 $hcu->purgeUrls( $this->getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2243 } else {
2244 # Update backlink pages pointing to this title if created
2245 $blcFactory = MediaWikiServices::getInstance()->getBacklinkCacheFactory();
2246 LinksUpdate::queueRecursiveJobsForTable(
2247 $this->getTitle(),
2248 'imagelinks',
2249 'upload-image',
2250 $performer->getUser()->getName(),
2251 $blcFactory->getBacklinkCache( $this->getTitle() )
2252 );
2253 }
2254
2255 $this->prerenderThumbnails();
2256 }
2257 );
2258
2259 # Invalidate cache for all pages using this file
2260 $cacheUpdateJob = HTMLCacheUpdateJob::newForBacklinks(
2261 $this->getTitle(),
2262 'imagelinks',
2263 [ 'causeAction' => 'file-upload', 'causeAgent' => $performer->getUser()->getName() ]
2264 );
2265
2266 // NOTE: We are probably still in the implicit transaction started by DBO_TRX. We should
2267 // only schedule jobs after that transaction was committed, so a job queue failure
2268 // doesn't cause the upload to fail (T263301). Also, we should generally not schedule any
2269 // Jobs or the DeferredUpdates that assume the update is complete until after the
2270 // transaction has been committed and we are sure that the upload was indeed successful.
2271 $dbw->onTransactionCommitOrIdle( static function () use ( $reupload, $purgeUpdate, $cacheUpdateJob ) {
2272 DeferredUpdates::addUpdate( $purgeUpdate, DeferredUpdates::PRESEND );
2273
2274 if ( !$reupload ) {
2275 // This is a new file, so update the image count
2276 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
2277 }
2278
2279 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $cacheUpdateJob );
2280 }, __METHOD__ );
2281
2282 return Status::newGood();
2283 }
2284
2301 public function publish( $src, $flags = 0, array $options = [] ) {
2302 return $this->publishTo( $src, $this->getRel(), $flags, $options );
2303 }
2304
2321 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
2322 $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
2323
2324 $repo = $this->getRepo();
2325 if ( $repo->getReadOnlyReason() !== false ) {
2326 return $this->readOnlyFatalStatus();
2327 }
2328
2329 $status = $this->acquireFileLock();
2330 if ( !$status->isOK() ) {
2331 return $status;
2332 }
2333
2334 if ( $this->isOld() ) {
2335 $archiveRel = $dstRel;
2336 $archiveName = basename( $archiveRel );
2337 } else {
2338 $archiveName = ConvertibleTimestamp::now( TS::MW ) . '!' . $this->getName();
2339 $archiveRel = $this->getArchiveRel( $archiveName );
2340 }
2341
2342 if ( $repo->hasSha1Storage() ) {
2343 $sha1 = FileRepo::isVirtualUrl( $srcPath )
2344 ? $repo->getFileSha1( $srcPath )
2345 : FSFile::getSha1Base36FromPath( $srcPath );
2347 $wrapperBackend = $repo->getBackend();
2348 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
2349 $dst = $wrapperBackend->getPathForSHA1( $sha1 );
2350 $status = $repo->quickImport( $src, $dst );
2351 if ( $flags & File::DELETE_SOURCE ) {
2352 unlink( $srcPath );
2353 }
2354
2355 if ( $this->exists() ) {
2356 $status->value = $archiveName;
2357 }
2358 } else {
2359 $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
2360 $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
2361
2362 if ( $status->value == 'new' ) {
2363 $status->value = '';
2364 } else {
2365 $status->value = $archiveName;
2366 }
2367 }
2368
2369 $this->releaseFileLock();
2370 return $status;
2371 }
2372
2391 public function move( $target ) {
2392 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2393 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2394 return $this->readOnlyFatalStatus();
2395 }
2396
2397 wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
2398 $batch = new LocalFileMoveBatch( $this, $target );
2399
2400 $status = $batch->addCurrent();
2401 if ( !$status->isOK() ) {
2402 return $status;
2403 }
2404 $archiveNames = $batch->addOlds();
2405 $status = $batch->execute();
2406
2407 wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
2408
2409 // Purge the source and target files outside the transaction...
2410 $oldTitleFile = $localRepo->newFile( $this->title );
2411 $newTitleFile = $localRepo->newFile( $target );
2412 DeferredUpdates::addUpdate(
2413 new AutoCommitUpdate(
2414 $this->getRepo()->getPrimaryDB(),
2415 __METHOD__,
2416 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
2417 $oldTitleFile->purgeEverything();
2418 foreach ( $archiveNames as $archiveName ) {
2420 '@phan-var OldLocalFile $oldTitleFile';
2421 $oldTitleFile->purgeOldThumbnails( $archiveName );
2422 }
2423 $newTitleFile->purgeEverything();
2424 }
2425 ),
2426 DeferredUpdates::PRESEND
2427 );
2428
2429 if ( $status->isOK() ) {
2430 // Now switch the object
2431 $this->title = $target;
2432 // Force regeneration of the name and hashpath
2433 $this->name = null;
2434 $this->hashPath = null;
2435 }
2436
2437 return $status;
2438 }
2439
2456 public function deleteFile( $reason, UserIdentity $user, $suppress = false ) {
2457 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2458 return $this->readOnlyFatalStatus();
2459 }
2460
2461 $batch = new LocalFileDeleteBatch( $this, $user, $reason, $suppress );
2462
2463 $batch->addCurrent();
2464 // Get old version relative paths
2465 $archiveNames = $batch->addOlds();
2466 $status = $batch->execute();
2467
2468 if ( $status->isOK() ) {
2469 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
2470 }
2471
2472 // To avoid slow purges in the transaction, move them outside...
2473 DeferredUpdates::addUpdate(
2474 new AutoCommitUpdate(
2475 $this->getRepo()->getPrimaryDB(),
2476 __METHOD__,
2477 function () use ( $archiveNames ) {
2478 $this->purgeEverything();
2479 foreach ( $archiveNames as $archiveName ) {
2480 $this->purgeOldThumbnails( $archiveName );
2481 }
2482 }
2483 ),
2484 DeferredUpdates::PRESEND
2485 );
2486
2487 // Purge the CDN
2488 $purgeUrls = [];
2489 foreach ( $archiveNames as $archiveName ) {
2490 $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2491 }
2492
2493 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2494 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2495
2496 return $status;
2497 }
2498
2516 public function deleteOldFile( $archiveName, $reason, UserIdentity $user, $suppress = false ) {
2517 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2518 return $this->readOnlyFatalStatus();
2519 }
2520
2521 $batch = new LocalFileDeleteBatch( $this, $user, $reason, $suppress );
2522
2523 $batch->addOld( $archiveName );
2524 $status = $batch->execute();
2525
2526 $this->purgeOldThumbnails( $archiveName );
2527 if ( $status->isOK() ) {
2528 $this->purgeDescription();
2529 }
2530
2531 $url = $this->getArchiveUrl( $archiveName );
2532 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2533 $hcu->purgeUrls( $url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2534
2535 return $status;
2536 }
2537
2550 public function restore( $versions = [], $unsuppress = false ) {
2551 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2552 return $this->readOnlyFatalStatus();
2553 }
2554
2555 $batch = new LocalFileRestoreBatch( $this, $unsuppress );
2556
2557 if ( !$versions ) {
2558 $batch->addAll();
2559 } else {
2560 $batch->addIds( $versions );
2561 }
2562 $status = $batch->execute();
2563 if ( $status->isGood() ) {
2564 $cleanupStatus = $batch->cleanup();
2565 $cleanupStatus->successCount = 0;
2566 $cleanupStatus->failCount = 0;
2567 $status->merge( $cleanupStatus );
2568 }
2569
2570 return $status;
2571 }
2572
2583 public function getDescriptionUrl() {
2584 // Avoid hard failure when the file does not exist. T221812
2585 return $this->title ? $this->title->getLocalURL() : false;
2586 }
2587
2597 public function getDescriptionText( Language $lang ) {
2598 if ( !$this->title ) {
2599 return false; // Avoid hard failure when the file does not exist. T221812
2600 }
2601
2602 $services = MediaWikiServices::getInstance();
2603 $page = $services->getPageStore()->getPageByReference( $this->getTitle() );
2604 if ( !$page ) {
2605 return false;
2606 }
2607
2608 $parserOptions = ParserOptions::newFromUserAndLang(
2609 RequestContext::getMain()->getUser(),
2610 $lang
2611 );
2612 $parseStatus = $services->getParserOutputAccess()
2613 ->getParserOutput( $page, $parserOptions );
2614
2615 if ( !$parseStatus->isGood() ) {
2616 // Rendering failed.
2617 return false;
2618 }
2619 // TODO T371004 move runOutputPipeline out of $parserOutput
2620 return $parseStatus->getValue()->runOutputPipeline( $parserOptions, [] )->getContentHolderText();
2621 }
2622
2630 public function getUploader( int $audience = self::FOR_PUBLIC, ?Authority $performer = null ): ?UserIdentity {
2631 $this->load();
2632 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
2633 return null;
2634 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $performer ) ) {
2635 return null;
2636 } else {
2637 return $this->user;
2638 }
2639 }
2640
2647 public function getDescription( $audience = self::FOR_PUBLIC, ?Authority $performer = null ) {
2648 $this->load();
2649 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2650 return '';
2651 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $performer ) ) {
2652 return '';
2653 } else {
2654 return $this->description;
2655 }
2656 }
2657
2662 public function getTimestamp() {
2663 $this->load();
2664
2665 return $this->timestamp;
2666 }
2667
2672 public function getDescriptionTouched() {
2673 if ( !$this->exists() ) {
2674 return false; // Avoid hard failure when the file does not exist. T221812
2675 }
2676
2677 // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
2678 // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
2679 // need to differentiate between null (uninitialized) and false (failed to load).
2680 if ( $this->descriptionTouched === null ) {
2681 $touched = $this->repo->getReplicaDB()->newSelectQueryBuilder()
2682 ->select( 'page_touched' )
2683 ->from( 'page' )
2684 ->where( [ 'page_namespace' => $this->title->getNamespace() ] )
2685 ->andWhere( [ 'page_title' => $this->title->getDBkey() ] )
2686 ->caller( __METHOD__ )->fetchField();
2687 $this->descriptionTouched = $touched ? wfTimestamp( TS::MW, $touched ) : false;
2688 }
2689
2690 return $this->descriptionTouched;
2691 }
2692
2697 public function getSha1() {
2698 $this->load();
2699 return $this->sha1;
2700 }
2701
2705 public function isCacheable() {
2706 $this->load();
2707
2708 // If extra data (metadata) was not loaded then it must have been large
2709 return $this->extraDataLoaded
2710 && strlen( serialize( $this->metadataArray ) ) <= self::CACHE_FIELD_MAX_LEN;
2711 }
2712
2725 public function acquireFileLock( $timeout = 0 ) {
2726 $status = Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2727 [ $this->getPath() ], LockManager::LOCK_EX, $timeout
2728 ) );
2729
2730 if ( !$status->isOK() ) {
2731 $logger = LoggerFactory::getInstance( 'LocalFile' );
2732 if ( $status->hasMessage( 'lockmanager-fail-conflict' ) ) {
2733 $errorKey = 'lockmanager-fail-conflict';
2734 } else {
2735 $messages = $status->getMessages( 'error' );
2736 $errorKey = $messages ? $messages[0]->getKey() : 'unknown';
2737 }
2738 $logger->warning(
2739 "Failed to lock '{file}'",
2740 [
2741 'file' => $this->name,
2742 'error_key' => $errorKey,
2743 'exception' => new RuntimeException()
2744 ]
2745 );
2746 }
2747
2748 return $status;
2749 }
2750
2757 public function releaseFileLock() {
2758 $status = Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2759 [ $this->getPath() ], LockManager::LOCK_EX
2760 ) );
2761
2762 if ( !$status->isOK() ) {
2763 $logger = LoggerFactory::getInstance( 'LocalFile' );
2764 $messages = $status->getMessages( 'error' );
2765 $errorKey = $messages ? $messages[0]->getKey() : 'unknown';
2766 $logger->error(
2767 "Failed to unlock '{file}'",
2768 [
2769 'file' => $this->name,
2770 'error_key' => $errorKey,
2771 'exception' => new RuntimeException()
2772 ]
2773 );
2774 }
2775
2776 return $status;
2777 }
2778
2789 public function lock() {
2790 wfDeprecated( __METHOD__, '1.38' );
2791 if ( !$this->locked ) {
2792 $logger = LoggerFactory::getInstance( 'LocalFile' );
2793
2794 $dbw = $this->repo->getPrimaryDB();
2795 $makesTransaction = !$dbw->trxLevel();
2796 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2797 // T56736: use simple lock to handle when the file does not exist.
2798 // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
2799 // Also, that would cause contention on INSERT of similarly named rows.
2800 $status = $this->acquireFileLock( 10 ); // represents all versions of the file
2801 if ( !$status->isGood() ) {
2802 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2803 $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
2804
2805 throw new LocalFileLockError( $status );
2806 }
2807 // Release the lock *after* commit to avoid row-level contention.
2808 // Make sure it triggers on rollback() as well as commit() (T132921).
2809 $dbw->onTransactionResolution(
2810 function () use ( $logger ) {
2811 $status = $this->releaseFileLock();
2812 if ( !$status->isGood() ) {
2813 $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
2814 }
2815 },
2816 __METHOD__
2817 );
2818 // Callers might care if the SELECT snapshot is safely fresh
2819 $this->lockedOwnTrx = $makesTransaction;
2820 }
2821
2822 $this->locked++;
2823
2824 return $this->lockedOwnTrx;
2825 }
2826
2837 public function unlock() {
2838 wfDeprecated( __METHOD__, '1.47' );
2839 if ( $this->locked ) {
2840 wfDeprecated( __METHOD__, '1.38' );
2841 --$this->locked;
2842 if ( !$this->locked ) {
2843 $dbw = $this->repo->getPrimaryDB();
2844 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2845 $this->lockedOwnTrx = false;
2846 }
2847 }
2848 }
2849
2853 protected function readOnlyFatalStatus() {
2854 return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
2855 $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
2856 }
2857
2861 public function __destruct() {
2862 if ( $this->locked ) {
2863 $this->unlock();
2864 }
2865 }
2866}
2867
2869class_alias( LocalFile::class, 'LocalFile' );
const SCHEMA_COMPAT_OLD
Definition Defines.php:305
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:293
const NS_FILE
Definition Defines.php:57
const EDIT_SUPPRESS_RC
Definition Defines.php:126
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:297
const EDIT_SILENT
Do not notify other users (e.g.
Definition Defines.php:123
const EDIT_NEW
Article is assumed to be non-existent, fail if it exists.
Definition Defines.php:114
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
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:71
Base class for content handling.
Group all the pieces relevant to the context of a request into one instance.
Deferrable Update for closure/callback updates that should use auto-commit mode.
Defer callable updates to run later in the PHP process.
Class the manages updates of *_link tables as well as similar extension-managed tables.
Class for handling updates to the site_stats table.
Proxy backend that manages file layout rewriting for FileRepo.
Base class for file repositories.
Definition FileRepo.php:51
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:80
FileRepo LocalRepo ForeignAPIRepo false $repo
Some member variables can be lazy-initialised using __get().
Definition File.php:127
Title string false $title
Definition File.php:130
Local file in the wiki's own database.
Definition LocalFile.php:80
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object.
isMissing()
splitMime inherited
invalidateCache()
Purge the file object/metadata cache.
loadFromRow( $row, $prefix='img_')
Load file metadata from a DB result row.
unprefixRow( $row, $prefix='img_')
getDescriptionText(Language $lang)
Get the HTML text of the description page This is not used by ImagePage for local files,...
getDescriptionShortUrl()
Get short description URL for a file based on the page ID.
getMetadataForDb(IReadableDatabase $db)
Serialize the metadata array for insertion into img_metadata, oi_metadata or fa_metadata.
getWidth( $page=1)
Return the width of the image.
publishTo( $src, $dstRel, $flags=0, array $options=[])
Move or copy a file to a specified location.
static newFromTitle( $title, $repo, $unused=null)
Create a LocalFile from a title Do not call this except from inside a repo class.
getMetadataItems(array $itemNames)
Get multiple elements of the unserialized handler-specific metadata.
int $filerevision_id
id in filerevision table, null on read old
int $file_id
id in file table, null on read old
string $sha1
SHA-1 base 36 content hash.
bool $fileExists
Does the file exist on disk? (loadFromXxx)
getMimeType()
Returns the MIME type of the file.
string $mime
MIME type, determined by MimeAnalyzer::guessMimeType.
int $bits
Returned by getimagesize (loadFromXxx)
getCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache.
string[] $unloadedMetadataBlobs
Map of metadata item name to blob address for items that exist but have not yet been loaded into $thi...
deleteFile( $reason, UserIdentity $user, $suppress=false)
Delete all versions of the file.
getLazyCacheFields( $prefix='img_')
Returns the list of object properties that are included as-is in the cache, only when they're not too...
getHeight( $page=1)
Return the height of the image.
setProps( $info)
Set properties in this object to be equal to those given in the associative array $info.
array $metadataArray
Unserialized metadata.
loadMetadataFromString( $metadataString)
Unserialize a metadata string which came from some non-DB source, or is the return value of IReadable...
string[] $metadataBlobs
Map of metadata item name to blob address.
loadMetadataFromDbFieldValue(IReadableDatabase $db, $metadataBlob)
Unserialize a metadata blob which came from the database and store it in $this.
load( $flags=0)
Load file metadata from cache or DB, unless already loaded.
getMetadata()
Get handler-specific metadata as a serialized string.
reserializeMetadata()
Write the metadata back to the database with the current serialization format.
static newFromRow( $row, $repo)
Create a LocalFile from a title Do not call this except from inside a repo class.
deleteOldFile( $archiveName, $reason, UserIdentity $user, $suppress=false)
Delete an old version of the file.
unlock()
Decrement the lock reference count and end the atomic section if it reaches zero.
acquireFileIdFromName()
This is mostly for the migration period.
int $size
Size in bytes (loadFromXxx)
loadFromFile( $path=null)
Load metadata from the file itself.
string $media_type
MEDIATYPE_xxx (bitmap, drawing, audio...)
getDescriptionUrl()
isMultipage inherited
getSize()
Returns the size of the image file, in bytes.
publish( $src, $flags=0, array $options=[])
Move or copy a file to its public location.
nextHistoryLine()
Returns the history of this file, line by line.
string null $metadataSerializationFormat
One of the MDS_* constants, giving the format of the metadata as stored in the DB,...
bool $dataLoaded
Whether or not core data has been loaded from the database (loadFromXxx)
lock()
Start an atomic DB section and lock the image for update or increments a reference counter if the loc...
acquireFileLock( $timeout=0)
Acquire an exclusive lock on the file, indicating an intention to perform writes to the registry data...
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
getThumbnails( $archiveName=false)
getTransformScript inherited
prerenderThumbnails()
Prerenders a configurable set of thumbnails.
getUploader(int $audience=self::FOR_PUBLIC, ?Authority $performer=null)
getFileIdFromName()
This is mostly for the migration period.
__construct( $title, $repo)
Do not call this except from inside a repo class.
getHistory( $limit=null, $start=null, $end=null, $inc=true)
purgeDescription inherited
getDescription( $audience=self::FOR_PUBLIC, ?Authority $performer=null)
__destruct()
Clean up any dangling locks.
move( $target)
getLinksTo inherited
recordUpload3(string $oldver, string $comment, string $pageText, Authority $performer, $props=false, $timestamp=false, $tags=[], bool $createDummyRevision=true, bool $revert=false)
Record a file upload in the upload log and the image table (version 3)
loadFromDB( $flags=0)
Load file metadata from the DB.
resetHistory()
Reset the history pointer to the first element of the history.
int $deleted
Bitfield akin to rev_deleted.
getMediaType()
Returns the type of the media in the file.
loadExtraFromDB()
Load lazy file metadata from the DB.
static newFromKey( $sha1, $repo, $timestamp=false)
Create a LocalFile from a SHA-1 key Do not call this except from inside a repo class.
purgeThumbnails( $options=[])
Delete cached transformed files for the current version only.
restore( $versions=[], $unsuppress=false)
Restore all or specified deleted revisions to the given file.
maybeUpgradeRow()
Upgrade a row if it needs it.
getCacheKey()
Get the memcached key for the main data for this file, or false if there is no access to the shared c...
releaseFileLock()
Release a lock acquired with acquireFileLock().
purgeCache( $options=[])
Delete all previously generated thumbnails, refresh metadata in memcached and purge the CDN.
purgeOldThumbnails( $archiveName)
Delete cached transformed files for an archived version only.
upload( $src, $comment, $pageText, $flags=0, $props=false, $timestamp=false, ?Authority $uploader=null, $tags=[], $createDummyRevision=true, $revert=false)
getHashPath inherited
purgeThumbList( $dir, $files)
Delete a list of thumbnails visible at urls.
upgradeRow()
Fix assorted version-related problems with the image row by reloading it from the file.
getMetadataArray()
Get unserialized handler-specific metadata.
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
Definition LocalRepo.php:44
Job to purge the HTML/file cache for all pages that link to or use another page or file.
Job for asynchronous rendering of thumbnails, e.g.
Base class for language-specific code.
Definition Language.php:65
Create PSR-3 logger objects.
Extends the LogEntry Interface with some basic functionality.
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Base media handler class.
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:66
Special handling for representing file pages.
Set options of the Parser.
Value object representing a content slot associated with a page revision.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Controller-like object for creating and updating pages by creating new revisions.
Represents a title within MediaWiki.
Definition Title.php:69
loadFromRow( $row)
Load Title object fields from a DB row.
Definition Title.php:561
Value object representing a user's identity.
MimeMagic helper wrapper.
Class representing a non-directory file on the file system.
Definition FSFile.php:20
File backend exception for checked exceptions (e.g.
Base class for all file backend classes (including multi-write backends).
Resource locking handling.
Build SELECT queries with a fluent interface.
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'=>[], 'DefaultLockManager'=> null, '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, '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, 'RestTermsOfServiceUrl'=> null, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, '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', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], '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,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> 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',],],], 'EnableSectionShare'=> false, '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, ], 'NamespacesWithoutAutoSummaries' => [ ], '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, '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, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], '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, ], '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, 'autocreateaccount' => 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, 'createwithcontentmodel' => true, 'logout' => 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, 'createpreviouslyrenamedaccount' => 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' => [ ], 'RestrictUserPageEditing' => false, '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, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => 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, 'createwithcontentmodel' => 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, 'createwithcontentmodel' => 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, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => 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', 'managesessions' => '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, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => 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, 'ApiClientErrorSampleRate' => 1.0, '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: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], '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, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], '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, 'EnableWatchstarPopover' => 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\\RecentChanges\\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', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'RestLocalModuleTestBaseUrl' => null, 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], '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, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], ], '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', 'DefaultLockManager' => [ 'string', 'null', ], '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', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'RestTermsOfServiceUrl' => [ 'string', 'null', ], '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', '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', 'NamespacesWithoutAutoSummaries' => 'array', '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', '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', 'RestrictUserPageEditing' => 'boolean', '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', 'ReauthenticateForActions' => 'object', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ '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', 'RestrictedTagViewRights' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => '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', 'RestLocalModuleTestBaseUrl' => [ 'string', 'null', ], 'RestModuleOverrides' => 'object', 'RestExternalModules' => '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', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', ], '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', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_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', ], ], ], ], ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], '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', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], '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', ], ], ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'mode' => [ 'type' => 'string', ], ], 'required' => [ 'mode', ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], '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.', ],]
This interface represents the authority associated with the current execution context,...
Definition Authority.php:23
getUser()
Returns the performer of the actions associated with this authority.
Interface for objects representing user identity.
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.
Interface for database access objects.
A database connection without write operations.
newSelectQueryBuilder()
Create an empty SelectQueryBuilder which can be used to run queries against this connection.
encodeBlob( $b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects.
expr(string $field, string $op, $value)
See Expression::__construct()
Result wrapper for grabbing data queried from an IDatabase object.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...