MediaWiki master
LocalFile.php
Go to the documentation of this file.
1<?php
8
9use InvalidArgumentException;
39use RuntimeException;
40use stdClass;
41use UnexpectedValueException;
52use Wikimedia\Timestamp\ConvertibleTimestamp;
53use Wikimedia\Timestamp\TimestampFormat as TS;
54
81class LocalFile extends File {
82 private const VERSION = 13; // cache version
83
84 private const CACHE_FIELD_MAX_LEN = 1000;
85
87 private const MDS_EMPTY = 'empty';
88
90 private const MDS_LEGACY = 'legacy';
91
93 private const MDS_PHP = 'php';
94
96 private const MDS_JSON = 'json';
97
99 private const MAX_PAGE_RENDER_JOBS = 50;
100
102 protected $fileExists;
103
105 private $fileId;
106
108 private $fileTypeId;
109
111 protected $width;
112
114 protected $height;
115
117 protected $bits;
118
120 protected $media_type;
121
123 protected $mime;
124
126 protected $size;
127
129 protected $metadataArray = [];
130
138
140 protected $metadataBlobs = [];
141
149
151 protected $sha1;
152
154 protected $dataLoaded = false;
155
157 protected $extraDataLoaded = false;
158
160 protected $deleted;
161
163 protected $file_id;
164
167
169 protected $repoClass = LocalRepo::class;
170
172 private $historyLine = 0;
173
175 private $historyRes = null;
176
178 private $major_mime;
179
181 private $minor_mime;
182
184 private $timestamp;
185
187 private $user;
188
190 private $description;
191
193 private $descriptionTouched;
194
196 private $upgraded = false;
197
199 private $upgrading;
200
202 private $locked;
203
205 private $lockedOwnTrx;
206
208 private $missing;
209
211 private $metadataStorageHelper;
212
214 private $migrationStage = SCHEMA_COMPAT_OLD;
215
216 // @note: higher than IDBAccessObject constants
217 private const LOAD_ALL = 16; // integer; load all the lazy fields too (like metadata)
218
219 private const ATOMIC_SECTION_LOCK = 'LocalFile::lockingTransaction';
220
233 public static function newFromTitle( $title, $repo, $unused = null ): static {
234 return new static( $title, $repo );
235 }
236
246 public static function newFromRow( $row, $repo ): static {
247 $title = Title::makeTitle( NS_FILE, $row->img_name );
248 $file = new static( $title, $repo );
249 $file->loadFromRow( $row );
250
251 return $file;
252 }
253
265 public static function newFromKey( $sha1, $repo, $timestamp = false ): static|false {
266 $dbr = $repo->getReplicaDB();
267 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
268
269 $queryBuilder->where( [ 'img_sha1' => $sha1 ] );
270
271 if ( $timestamp ) {
272 $queryBuilder->andWhere( [ 'img_timestamp' => $dbr->timestamp( $timestamp ) ] );
273 }
274
275 $row = $queryBuilder->caller( __METHOD__ )->fetchRow();
276 if ( $row ) {
277 return static::newFromRow( $row, $repo );
278 } else {
279 return false;
280 }
281 }
282
303 public static function getQueryInfo( array $options = [] ) {
304 wfDeprecated( __METHOD__, '1.41' );
305 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
306 $queryInfo = FileSelectQueryBuilder::newForFile( $dbr, $options )->getQueryInfo();
307 // needs remapping...
308 return [
309 'tables' => $queryInfo['tables'],
310 'fields' => $queryInfo['fields'],
311 'joins' => $queryInfo['join_conds'],
312 ];
313 }
314
322 public function __construct( $title, $repo ) {
323 parent::__construct( $title, $repo );
324 $this->metadataStorageHelper = new MetadataStorageHelper( $repo );
325 $this->migrationStage = MediaWikiServices::getInstance()->getMainConfig()->get(
326 MainConfigNames::FileSchemaMigrationStage
327 );
328
329 $this->assertRepoDefined();
330 $this->assertTitleDefined();
331 }
332
336 public function getRepo() {
337 return $this->repo;
338 }
339
346 protected function getCacheKey() {
347 return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
348 }
349
353 private function loadFromCache() {
354 $this->dataLoaded = false;
355 $this->extraDataLoaded = false;
356
357 $key = $this->getCacheKey();
358 if ( !$key ) {
359 $this->loadFromDB( IDBAccessObject::READ_NORMAL );
360
361 return;
362 }
363
364 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
365 $cachedValues = $cache->getWithSetCallback(
366 $key,
367 $cache::TTL_WEEK,
368 function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
369 $setOpts += Database::getCacheSetOptions( $this->repo->getReplicaDB() );
370
371 $this->loadFromDB( IDBAccessObject::READ_NORMAL );
372
373 $fields = $this->getCacheFields( '' );
374 $cacheVal = [];
375 $cacheVal['fileExists'] = $this->fileExists;
376 if ( $this->fileExists ) {
377 foreach ( $fields as $field ) {
378 $cacheVal[$field] = $this->$field;
379 }
380 }
381 if ( $this->user ) {
382 $cacheVal['user'] = $this->user->getId();
383 $cacheVal['user_text'] = $this->user->getName();
384 }
385
386 // Don't cache metadata items stored as blobs, since they tend to be large
387 if ( $this->metadataBlobs ) {
388 $cacheVal['metadata'] = array_diff_key(
389 $this->metadataArray, $this->metadataBlobs );
390 // Save the blob addresses
391 $cacheVal['metadataBlobs'] = $this->metadataBlobs;
392 } else {
393 $cacheVal['metadata'] = $this->metadataArray;
394 }
395
396 // Strip off excessive entries from the subset of fields that can become large.
397 // If the cache value gets too large and might not fit in the cache,
398 // causing repeat database queries for each access to the file.
399 foreach ( $this->getLazyCacheFields( '' ) as $field ) {
400 if ( isset( $cacheVal[$field] )
401 && strlen( serialize( $cacheVal[$field] ) ) > 100 * 1024
402 ) {
403 unset( $cacheVal[$field] ); // don't let the value get too big
404 if ( $field === 'metadata' ) {
405 unset( $cacheVal['metadataBlobs'] );
406 }
407 }
408 }
409
410 if ( $this->fileExists ) {
411 $ttl = $cache->adaptiveTTL( (int)wfTimestamp( TS::UNIX, $this->timestamp ), $ttl );
412 } else {
413 $ttl = $cache::TTL_DAY;
414 }
415
416 return $cacheVal;
417 },
418 [ 'version' => self::VERSION ]
419 );
420
421 $this->fileExists = $cachedValues['fileExists'];
422 if ( $this->fileExists ) {
423 $this->setProps( $cachedValues );
424 }
425
426 $this->dataLoaded = true;
427 $this->extraDataLoaded = true;
428 foreach ( $this->getLazyCacheFields( '' ) as $field ) {
429 $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
430 }
431 }
432
436 public function invalidateCache() {
437 $key = $this->getCacheKey();
438 if ( !$key ) {
439 return;
440 }
441
442 $this->repo->getPrimaryDB()->onTransactionPreCommitOrIdle(
443 static function () use ( $key ) {
444 MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
445 },
446 __METHOD__
447 );
448 }
449
457 public function loadFromFile( $path = null ) {
458 $props = $this->repo->getFileProps( $path ?? $this->getVirtualUrl() );
459 $this->setProps( $props );
460 }
461
469 protected function getCacheFields( $prefix = 'img_' ) {
470 if ( $prefix !== '' ) {
471 throw new InvalidArgumentException(
472 __METHOD__ . ' with a non-empty prefix is no longer supported.'
473 );
474 }
475
476 // See self::getQueryInfo() for the fetching of the data from the DB,
477 // self::loadFromRow() for the loading of the object from the DB row,
478 // and self::loadFromCache() for the caching, and self::setProps() for
479 // populating the object from an array of data.
480 return [ 'size', 'width', 'height', 'bits', 'media_type',
481 'major_mime', 'minor_mime', 'timestamp', 'sha1', 'description' ];
482 }
483
491 protected function getLazyCacheFields( $prefix = 'img_' ) {
492 if ( $prefix !== '' ) {
493 throw new InvalidArgumentException(
494 __METHOD__ . ' with a non-empty prefix is no longer supported.'
495 );
496 }
497
498 // Keep this in sync with the omit-lazy option in self::getQueryInfo().
499 return [ 'metadata' ];
500 }
501
507 protected function loadFromDB( $flags = 0 ) {
508 $fname = static::class . '::' . __FUNCTION__;
509
510 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
511 $this->dataLoaded = true;
512 $this->extraDataLoaded = true;
513
514 $dbr = ( $flags & IDBAccessObject::READ_LATEST )
515 ? $this->repo->getPrimaryDB()
516 : $this->repo->getReplicaDB();
517 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
518
519 $queryBuilder->where( [ 'img_name' => $this->getName() ] );
520 $row = $queryBuilder->caller( $fname )->fetchRow();
521
522 if ( $row ) {
523 $this->loadFromRow( $row );
524 } else {
525 $this->fileExists = false;
526 $this->mime = 'unknown/unknown';
527 }
528 }
529
535 protected function loadExtraFromDB() {
536 if ( !$this->title ) {
537 return; // Avoid hard failure when the file does not exist. T221812
538 }
539
540 $fname = static::class . '::' . __FUNCTION__;
541
542 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
543 $this->extraDataLoaded = true;
544
545 $db = $this->repo->getReplicaDB();
546 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
547 if ( !$fieldMap ) {
548 $db = $this->repo->getPrimaryDB();
549 $fieldMap = $this->loadExtraFieldsWithTimestamp( $db, $fname );
550 }
551
552 if ( $fieldMap ) {
553 if ( isset( $fieldMap['metadata'] ) ) {
554 $this->loadMetadataFromDbFieldValue( $db, $fieldMap['metadata'] );
555 }
556 } else {
557 throw new RuntimeException( "Could not find data for image '{$this->getName()}'." );
558 }
559 }
560
566 private function loadExtraFieldsWithTimestamp( IReadableDatabase $dbr, $fname ) {
567 $fieldMap = false;
568
569 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
570 $queryBuilder->where( [ 'img_name' => $this->getName() ] )
571 ->andWhere( [ 'img_timestamp' => $dbr->timestamp( $this->getTimestamp() ) ] );
572 $row = $queryBuilder->caller( $fname )->fetchRow();
573 if ( $row ) {
574 $fieldMap = $this->unprefixRow( $row, 'img_' );
575 } else {
576 // File may have been uploaded over in the meantime; check the old versions
577 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
578 $row = $queryBuilder->where( [ 'oi_name' => $this->getName() ] )
579 ->andWhere( [ 'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() ) ] )
580 ->caller( __METHOD__ )->fetchRow();
581 if ( $row ) {
582 $fieldMap = $this->unprefixRow( $row, 'oi_' );
583 }
584 }
585
586 return $fieldMap;
587 }
588
594 protected function unprefixRow( $row, $prefix = 'img_' ) {
595 $array = (array)$row;
596 $prefixLength = strlen( $prefix );
597
598 // Double check prefix once
599 if ( !str_starts_with( array_key_first( $array ), $prefix ) ) {
600 throw new InvalidArgumentException( __METHOD__ . ': incorrect $prefix parameter' );
601 }
602
603 $decoded = [];
604 foreach ( $array as $name => $value ) {
605 $decoded[substr( $name, $prefixLength )] = $value;
606 }
607
608 return $decoded;
609 }
610
626 public function loadFromRow( $row, $prefix = 'img_' ) {
627 $this->dataLoaded = true;
628
629 $unprefixed = $this->unprefixRow( $row, $prefix );
630
631 $this->name = $unprefixed['name'];
632 $this->media_type = $unprefixed['media_type'];
633
634 $services = MediaWikiServices::getInstance();
635 $this->description = $services->getCommentStore()
636 ->getComment( "{$prefix}description", $row )->text;
637
638 $this->user = $services->getUserFactory()->newFromAnyId(
639 $unprefixed['user'] ?? null,
640 $unprefixed['user_text'] ?? null,
641 $unprefixed['actor'] ?? null
642 );
643
644 $this->timestamp = wfTimestamp( TS::MW, $unprefixed['timestamp'] );
645
646 $this->loadMetadataFromDbFieldValue(
647 $this->repo->getReplicaDB(), $unprefixed['metadata'] );
648
649 if ( empty( $unprefixed['major_mime'] ) ) {
650 $this->major_mime = 'unknown';
651 $this->minor_mime = 'unknown';
652 $this->mime = 'unknown/unknown';
653 } else {
654 if ( !$unprefixed['minor_mime'] ) {
655 $unprefixed['minor_mime'] = 'unknown';
656 }
657 $this->major_mime = $unprefixed['major_mime'];
658 $this->minor_mime = $unprefixed['minor_mime'];
659 $this->mime = $unprefixed['major_mime'] . '/' . $unprefixed['minor_mime'];
660 }
661
662 // Trim zero padding from char/binary field
663 $this->sha1 = rtrim( $unprefixed['sha1'], "\0" );
664
665 // Normalize some fields to integer type, per their database definition.
666 // Use unary + so that overflows will be upgraded to double instead of
667 // being truncated as with intval(). This is important to allow > 2 GiB
668 // files on 32-bit systems.
669 $this->size = +$unprefixed['size'];
670 $this->width = +$unprefixed['width'];
671 $this->height = +$unprefixed['height'];
672 $this->bits = +$unprefixed['bits'];
673
674 // Check for extra fields (deprecated since MW 1.37)
675 $extraFields = array_diff(
676 array_keys( $unprefixed ),
677 [
678 'name', 'media_type', 'description_text', 'description_data',
679 'description_cid', 'user', 'user_text', 'actor', 'timestamp',
680 'metadata', 'major_mime', 'minor_mime', 'sha1', 'size', 'width',
681 'height', 'bits', 'file_id', 'filerevision_id'
682 ]
683 );
684 if ( $extraFields ) {
686 'Passing extra fields (' .
687 implode( ', ', $extraFields )
688 . ') to ' . __METHOD__ . ' was deprecated in MediaWiki 1.37. ' .
689 'Property assignment will be removed in a later version.',
690 '1.37' );
691 foreach ( $extraFields as $field ) {
692 $this->$field = $unprefixed[$field];
693 }
694 }
695
696 $this->fileExists = true;
697 }
698
704 public function load( $flags = 0 ) {
705 if ( !$this->dataLoaded ) {
706 if ( $flags & IDBAccessObject::READ_LATEST ) {
707 $this->loadFromDB( $flags );
708 } else {
709 $this->loadFromCache();
710 }
711 }
712
713 if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
714 // @note: loads on name/timestamp to reduce race condition problems
715 $this->loadExtraFromDB();
716 }
717 }
718
723 public function maybeUpgradeRow() {
724 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() || $this->upgrading ) {
725 return;
726 }
727
728 $upgrade = false;
729 $reserialize = false;
730 if ( $this->media_type === null || $this->mime == 'image/svg' ) {
731 $upgrade = true;
732 } else {
733 $handler = $this->getHandler();
734 if ( $handler ) {
735 $validity = $handler->isFileMetadataValid( $this );
736 if ( $validity === MediaHandler::METADATA_BAD ) {
737 $upgrade = true;
738 } elseif ( $validity === MediaHandler::METADATA_COMPATIBLE
739 && $this->repo->isMetadataUpdateEnabled()
740 ) {
741 $upgrade = true;
742 } elseif ( $this->repo->isJsonMetadataEnabled()
743 && $this->repo->isMetadataReserializeEnabled()
744 ) {
745 if ( $this->repo->isSplitMetadataEnabled() && $this->isMetadataOversize() ) {
746 $reserialize = true;
747 } elseif ( $this->metadataSerializationFormat !== self::MDS_EMPTY &&
748 $this->metadataSerializationFormat !== self::MDS_JSON ) {
749 $reserialize = true;
750 }
751 }
752 }
753 }
754
755 if ( $upgrade || $reserialize ) {
756 $this->upgrading = true;
757 // Defer updates unless in auto-commit CLI mode
758 DeferredUpdates::addCallableUpdate( function () use ( $upgrade ) {
759 $this->upgrading = false; // avoid duplicate updates
760 try {
761 if ( $upgrade ) {
762 $this->upgradeRow();
763 } else {
764 $this->reserializeMetadata();
765 }
766 } catch ( LocalFileLockError ) {
767 // let the other process handle it (or do it next time)
768 }
769 } );
770 }
771 }
772
776 public function getUpgraded() {
777 return $this->upgraded;
778 }
779
786 public function getFileIdFromName() {
787 if ( !$this->fileId ) {
788 $dbw = $this->repo->getPrimaryDB();
789 $id = $dbw->newSelectQueryBuilder()
790 ->select( 'file_id' )
791 ->from( 'file' )
792 ->where( [
793 'file_name' => $this->getName(),
794 'file_deleted' => 0
795 ] )
796 ->caller( __METHOD__ )
797 ->fetchField();
798 $this->fileId = $id;
799 }
800
801 return $this->fileId;
802 }
803
810 public function acquireFileIdFromName() {
811 $dbw = $this->repo->getPrimaryDB();
812 $id = $this->getFileIdFromName();
813 if ( $id ) {
814 return $id;
815 }
816 $id = $dbw->newSelectQueryBuilder()
817 ->select( 'file_id' )
818 ->from( 'file' )
819 ->where( [
820 'file_name' => $this->getName(),
821 ] )
822 ->caller( __METHOD__ )
823 ->fetchField();
824 if ( !$id ) {
825 $dbw->newInsertQueryBuilder()
826 ->insertInto( 'file' )
827 ->row( [
828 'file_name' => $this->getName(),
829 // The value will be updated later
830 'file_latest' => 0,
831 'file_deleted' => 0,
832 'file_type' => $this->getFileTypeId(),
833 ] )
834 ->caller( __METHOD__ )->execute();
835 $insertId = $dbw->insertId();
836 if ( !$insertId ) {
837 throw new RuntimeException( 'File entry could not be inserted' );
838 }
839 return $insertId;
840 } else {
841 // Undelete
842 $dbw->newUpdateQueryBuilder()
843 ->update( 'file' )
844 ->set( [ 'file_deleted' => 0 ] )
845 ->where( [ 'file_id' => $id ] )
846 ->caller( __METHOD__ )->execute();
847 return $id;
848 }
849 }
850
851 protected function getFileTypeId(): int {
852 if ( $this->fileTypeId ) {
853 return $this->fileTypeId;
854 }
855 [ $major, $minor ] = self::splitMime( $this->mime );
856 $dbw = $this->repo->getPrimaryDB();
857 $id = $dbw->newSelectQueryBuilder()
858 ->select( 'ft_id' )
859 ->from( 'filetypes' )
860 ->where( [
861 'ft_media_type' => $this->getMediaType(),
862 'ft_major_mime' => $major,
863 'ft_minor_mime' => $minor,
864 ] )
865 ->caller( __METHOD__ )
866 ->fetchField();
867 if ( $id ) {
868 $this->fileTypeId = $id;
869 return $id;
870 }
871 $dbw->newInsertQueryBuilder()
872 ->insertInto( 'filetypes' )
873 ->row( [
874 'ft_media_type' => $this->getMediaType(),
875 'ft_major_mime' => $major,
876 'ft_minor_mime' => $minor,
877 ] )
878 ->caller( __METHOD__ )->execute();
879
880 $id = $dbw->insertId();
881 if ( !$id ) {
882 throw new RuntimeException( 'File entry could not be inserted' );
883 }
884
885 $this->fileTypeId = $id;
886 return $id;
887 }
888
893 public function upgradeRow() {
894 $dbw = $this->repo->getPrimaryDB();
895
896 // Make a DB query condition that will fail to match the image row if the
897 // image was reuploaded while the upgrade was in process.
898 $freshnessCondition = [ 'img_timestamp' => $dbw->timestamp( $this->getTimestamp() ) ];
899
900 $this->loadFromFile();
901
902 # Don't destroy file info of missing files
903 if ( !$this->fileExists ) {
904 wfDebug( __METHOD__ . ": file does not exist, aborting" );
905
906 return;
907 }
908
909 [ $major, $minor ] = self::splitMime( $this->mime );
910
911 wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema" );
912
913 $metadata = $this->getMetadataForDb( $dbw );
914 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
915 $dbw->newUpdateQueryBuilder()
916 ->update( 'image' )
917 ->set( [
918 'img_size' => $this->size,
919 'img_width' => $this->width,
920 'img_height' => $this->height,
921 'img_bits' => $this->bits,
922 'img_media_type' => $this->media_type,
923 'img_major_mime' => $major,
924 'img_minor_mime' => $minor,
925 'img_metadata' => $metadata,
926 'img_sha1' => $this->sha1,
927 ] )
928 ->where( [ 'img_name' => $this->getName() ] )
929 ->andWhere( $freshnessCondition )
930 ->caller( __METHOD__ )->execute();
931 }
932
933 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
934 $dbw->newUpdateQueryBuilder()
935 ->update( 'filerevision' )
936 ->set( [
937 'fr_size' => $this->size,
938 'fr_width' => $this->width,
939 'fr_height' => $this->height,
940 'fr_bits' => $this->bits,
941 'fr_metadata' => $metadata,
942 'fr_sha1' => $this->sha1,
943 ] )
944 ->where( [ 'fr_file' => $this->acquireFileIdFromName() ] )
945 ->andWhere( [ 'fr_timestamp' => $dbw->timestamp( $this->getTimestamp() ) ] )
946 ->caller( __METHOD__ )->execute();
947 }
948
949 $this->invalidateCache();
950
951 $this->upgraded = true; // avoid rework/retries
952 }
953
958 protected function reserializeMetadata() {
959 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
960 return;
961 }
962 $dbw = $this->repo->getPrimaryDB();
963 $metadata = $this->getMetadataForDb( $dbw );
964 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
965 $dbw->newUpdateQueryBuilder()
966 ->update( 'image' )
967 ->set( [ 'img_metadata' => $metadata ] )
968 ->where( [
969 'img_name' => $this->name,
970 'img_timestamp' => $dbw->timestamp( $this->timestamp ),
971 ] )
972 ->caller( __METHOD__ )->execute();
973 }
974 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
975 $dbw->newUpdateQueryBuilder()
976 ->update( 'filerevision' )
977 ->set( [ 'fr_metadata' => $metadata ] )
978 ->where( [ 'fr_file' => $this->acquireFileIdFromName() ] )
979 ->andWhere( [ 'fr_timestamp' => $dbw->timestamp( $this->getTimestamp() ) ] )
980 ->caller( __METHOD__ )->execute();
981 }
982 $this->upgraded = true;
983 }
984
997 public function setProps( $info ) {
998 $this->dataLoaded = true;
999 $fields = $this->getCacheFields( '' );
1000 $fields[] = 'fileExists';
1001
1002 foreach ( $fields as $field ) {
1003 if ( isset( $info[$field] ) ) {
1004 $this->$field = $info[$field];
1005 }
1006 }
1007
1008 // Only our own cache sets these properties, so they both should be present.
1009 if ( isset( $info['user'] ) &&
1010 isset( $info['user_text'] ) &&
1011 $info['user_text'] !== ''
1012 ) {
1013 $this->user = new UserIdentityValue( $info['user'], $info['user_text'] );
1014 }
1015
1016 // Fix up mime fields
1017 if ( isset( $info['major_mime'] ) ) {
1018 $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
1019 } elseif ( isset( $info['mime'] ) ) {
1020 $this->mime = $info['mime'];
1021 [ $this->major_mime, $this->minor_mime ] = self::splitMime( $this->mime );
1022 }
1023
1024 if ( isset( $info['metadata'] ) ) {
1025 if ( is_string( $info['metadata'] ) ) {
1026 $this->loadMetadataFromString( $info['metadata'] );
1027 } elseif ( is_array( $info['metadata'] ) ) {
1028 $this->metadataArray = $info['metadata'];
1029 if ( isset( $info['metadataBlobs'] ) ) {
1030 $this->metadataBlobs = $info['metadataBlobs'];
1031 $this->unloadedMetadataBlobs = array_diff_key(
1032 $this->metadataBlobs,
1033 $this->metadataArray
1034 );
1035 } else {
1036 $this->metadataBlobs = [];
1037 $this->unloadedMetadataBlobs = [];
1038 }
1039 } else {
1040 $logger = LoggerFactory::getInstance( 'LocalFile' );
1041 $logger->warning( __METHOD__ . ' given invalid metadata of type ' .
1042 get_debug_type( $info['metadata'] ) );
1043 $this->metadataArray = [];
1044 }
1045 $this->extraDataLoaded = true;
1046 }
1047 }
1048
1064 public function isMissing() {
1065 if ( $this->missing === null ) {
1066 $fileExists = $this->repo->fileExists( $this->getVirtualUrl() );
1067 $this->missing = !$fileExists;
1068 }
1069
1070 return $this->missing;
1071 }
1072
1080 public function getWidth( $page = 1 ) {
1081 $page = (int)$page;
1082 if ( $page < 1 ) {
1083 $page = 1;
1084 }
1085
1086 $this->load();
1087
1088 if ( $this->isMultipage() ) {
1089 $handler = $this->getHandler();
1090 if ( !$handler ) {
1091 return 0;
1092 }
1093 $dim = $handler->getPageDimensions( $this, $page );
1094 if ( $dim ) {
1095 return $dim['width'];
1096 } else {
1097 // For non-paged media, the false goes through an
1098 // intval, turning failure into 0, so do same here.
1099 return 0;
1100 }
1101 } else {
1102 return $this->width;
1103 }
1104 }
1105
1113 public function getHeight( $page = 1 ) {
1114 $page = (int)$page;
1115 if ( $page < 1 ) {
1116 $page = 1;
1117 }
1118
1119 $this->load();
1120
1121 if ( $this->isMultipage() ) {
1122 $handler = $this->getHandler();
1123 if ( !$handler ) {
1124 return 0;
1125 }
1126 $dim = $handler->getPageDimensions( $this, $page );
1127 if ( $dim ) {
1128 return $dim['height'];
1129 } else {
1130 // For non-paged media, the false goes through an
1131 // intval, turning failure into 0, so do same here.
1132 return 0;
1133 }
1134 } else {
1135 return $this->height;
1136 }
1137 }
1138
1146 public function getDescriptionShortUrl() {
1147 if ( !$this->title ) {
1148 return null; // Avoid hard failure when the file does not exist. T221812
1149 }
1150
1151 $pageId = $this->title->getArticleID();
1152
1153 if ( $pageId ) {
1154 $url = $this->repo->makeUrl( [ 'curid' => $pageId ] );
1155 if ( $url !== false ) {
1156 return $url;
1157 }
1158 }
1159 return null;
1160 }
1161
1168 public function getMetadata() {
1169 $data = $this->getMetadataArray();
1170 if ( !$data ) {
1171 return '';
1172 } elseif ( array_keys( $data ) === [ '_error' ] ) {
1173 // Legacy error encoding
1174 return $data['_error'];
1175 } else {
1176 return serialize( $this->getMetadataArray() );
1177 }
1178 }
1179
1186 public function getMetadataArray(): array {
1187 $this->load( self::LOAD_ALL );
1188 if ( $this->unloadedMetadataBlobs ) {
1189 return $this->getMetadataItems(
1190 array_unique( array_merge(
1191 array_keys( $this->metadataArray ),
1192 array_keys( $this->unloadedMetadataBlobs )
1193 ) )
1194 );
1195 }
1196 return $this->metadataArray;
1197 }
1198
1199 public function getMetadataItems( array $itemNames ): array {
1200 $this->load( self::LOAD_ALL );
1201 $result = [];
1202 $addresses = [];
1203 foreach ( $itemNames as $itemName ) {
1204 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
1205 $result[$itemName] = $this->metadataArray[$itemName];
1206 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
1207 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
1208 }
1209 }
1210
1211 if ( $addresses ) {
1212 $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses );
1213 foreach ( $addresses as $itemName => $address ) {
1214 unset( $this->unloadedMetadataBlobs[$itemName] );
1215 $value = $resultFromBlob[$itemName] ?? null;
1216 if ( $value !== null ) {
1217 $result[$itemName] = $value;
1218 $this->metadataArray[$itemName] = $value;
1219 }
1220 }
1221 }
1222 return $result;
1223 }
1224
1236 public function getMetadataForDb( IReadableDatabase $db ) {
1237 $this->load( self::LOAD_ALL );
1238 if ( !$this->metadataArray && !$this->metadataBlobs ) {
1239 $s = '';
1240 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
1241 $s = $this->getJsonMetadata();
1242 } else {
1243 $s = serialize( $this->getMetadataArray() );
1244 }
1245 if ( !is_string( $s ) ) {
1246 throw new RuntimeException( 'Could not serialize image metadata value for DB' );
1247 }
1248 return $db->encodeBlob( $s );
1249 }
1250
1257 private function getJsonMetadata() {
1258 // Directly store data that is not already in BlobStore
1259 $envelope = [
1260 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
1261 ];
1262
1263 // Also store the blob addresses
1264 if ( $this->metadataBlobs ) {
1265 $envelope['blobs'] = $this->metadataBlobs;
1266 }
1267
1268 [ $s, $blobAddresses ] = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope );
1269
1270 // Repeated calls to this function should not keep inserting more blobs
1271 $this->metadataBlobs += $blobAddresses;
1272
1273 return $s;
1274 }
1275
1282 private function isMetadataOversize() {
1283 if ( !$this->repo->isSplitMetadataEnabled() ) {
1284 return false;
1285 }
1286 $threshold = $this->repo->getSplitMetadataThreshold();
1287 $directItems = array_diff_key( $this->metadataArray, $this->metadataBlobs );
1288 foreach ( $directItems as $value ) {
1289 if ( strlen( $this->metadataStorageHelper->jsonEncode( $value ) ) > $threshold ) {
1290 return true;
1291 }
1292 }
1293 return false;
1294 }
1295
1304 protected function loadMetadataFromDbFieldValue( IReadableDatabase $db, $metadataBlob ) {
1305 $this->loadMetadataFromString( $db->decodeBlob( $metadataBlob ) );
1306 }
1307
1315 protected function loadMetadataFromString( $metadataString ) {
1316 $this->extraDataLoaded = true;
1317 $this->metadataArray = [];
1318 $this->metadataBlobs = [];
1319 $this->unloadedMetadataBlobs = [];
1320 $metadataString = (string)$metadataString;
1321 if ( $metadataString === '' ) {
1322 $this->metadataSerializationFormat = self::MDS_EMPTY;
1323 return;
1324 }
1325 if ( $metadataString[0] === '{' ) {
1326 $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString );
1327 if ( !$envelope ) {
1328 // Legacy error encoding
1329 $this->metadataArray = [ '_error' => $metadataString ];
1330 $this->metadataSerializationFormat = self::MDS_LEGACY;
1331 } else {
1332 $this->metadataSerializationFormat = self::MDS_JSON;
1333 if ( isset( $envelope['data'] ) ) {
1334 $this->metadataArray = $envelope['data'];
1335 }
1336 if ( isset( $envelope['blobs'] ) ) {
1337 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope['blobs'];
1338 }
1339 }
1340 } else {
1341 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1342 $data = @unserialize( $metadataString );
1343 if ( !is_array( $data ) ) {
1344 // Legacy error encoding
1345 $data = [ '_error' => $metadataString ];
1346 $this->metadataSerializationFormat = self::MDS_LEGACY;
1347 } else {
1348 $this->metadataSerializationFormat = self::MDS_PHP;
1349 }
1350 $this->metadataArray = $data;
1351 }
1352 }
1353
1358 public function getBitDepth() {
1359 $this->load();
1360
1361 return (int)$this->bits;
1362 }
1363
1369 public function getSize() {
1370 $this->load();
1371
1372 return $this->size;
1373 }
1374
1380 public function getMimeType() {
1381 $this->load();
1382
1383 return $this->mime;
1384 }
1385
1392 public function getMediaType() {
1393 $this->load();
1394
1395 return $this->media_type;
1396 }
1397
1409 public function exists() {
1410 $this->load();
1411
1412 return $this->fileExists;
1413 }
1414
1436 protected function getThumbnails( $archiveName = false ) {
1437 if ( $archiveName ) {
1438 $dir = $this->getArchiveThumbPath( $archiveName );
1439 } else {
1440 $dir = $this->getThumbPath();
1441 }
1442
1443 $backend = $this->repo->getBackend();
1444 $files = [ $dir ];
1445 try {
1446 $iterator = $backend->getFileList( [ 'dir' => $dir, 'forWrite' => true ] );
1447 if ( $iterator !== null ) {
1448 foreach ( $iterator as $file ) {
1449 $files[] = $file;
1450 }
1451 }
1452 } catch ( FileBackendError ) {
1453 } // suppress (T56674)
1454
1455 return $files;
1456 }
1457
1466 public function purgeCache( $options = [] ) {
1467 // Refresh metadata in memcached, but don't touch thumbnails or CDN
1468 $this->maybeUpgradeRow();
1469 $this->invalidateCache();
1470
1471 // Delete thumbnails
1472 $this->purgeThumbnails( $options );
1473
1474 // Purge CDN cache for this file
1475 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
1476 $hcu->purgeUrls(
1477 $this->getUrl(),
1478 !empty( $options['forThumbRefresh'] )
1479 ? $hcu::PURGE_PRESEND // just a manual purge
1480 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1481 );
1482 }
1483
1489 public function purgeOldThumbnails( $archiveName ) {
1490 // Get a list of old thumbnails
1491 $thumbs = $this->getThumbnails( $archiveName );
1492
1493 // Delete thumbnails from storage, and prevent the directory itself from being purged
1494 $dir = array_shift( $thumbs );
1495 $this->purgeThumbList( $dir, $thumbs );
1496
1497 $urls = [];
1498 foreach ( $thumbs as $thumb ) {
1499 $urls[] = $this->getArchiveThumbUrl( $archiveName, $thumb );
1500 }
1501
1502 // Purge any custom thumbnail caches
1503 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, $archiveName, $urls );
1504
1505 // Purge the CDN
1506 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
1507 $hcu->purgeUrls( $urls, $hcu::PURGE_PRESEND );
1508 }
1509
1516 public function purgeThumbnails( $options = [] ) {
1517 $thumbs = $this->getThumbnails();
1518
1519 // Delete thumbnails from storage, and prevent the directory itself from being purged
1520 $dir = array_shift( $thumbs );
1521 $this->purgeThumbList( $dir, $thumbs );
1522
1523 // Always purge all files from CDN regardless of handler filters
1524 $urls = [];
1525 foreach ( $thumbs as $thumb ) {
1526 $urls[] = $this->getThumbUrl( $thumb );
1527 }
1528
1529 // Give the media handler a chance to filter the file purge list
1530 if ( !empty( $options['forThumbRefresh'] ) ) {
1531 $handler = $this->getHandler();
1532 if ( $handler ) {
1533 $handler->filterThumbnailPurgeList( $thumbs, $options );
1534 }
1535 }
1536
1537 // Purge any custom thumbnail caches
1538 $this->getHookRunner()->onLocalFilePurgeThumbnails( $this, false, $urls );
1539
1540 // Purge the CDN
1541 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
1542 $hcu->purgeUrls(
1543 $urls,
1544 !empty( $options['forThumbRefresh'] )
1545 ? $hcu::PURGE_PRESEND // just a manual purge
1546 : $hcu::PURGE_INTENT_TXROUND_REFLECTED
1547 );
1548 }
1549
1556 public function prerenderThumbnails() {
1557 $uploadThumbnailRenderMap = MediaWikiServices::getInstance()
1558 ->getMainConfig()->get( MainConfigNames::UploadThumbnailRenderMap );
1559
1560 $jobs = [];
1561
1562 $sizes = $uploadThumbnailRenderMap;
1563 rsort( $sizes );
1564
1565 foreach ( $sizes as $size ) {
1566 if ( $this->isMultipage() ) {
1567 // (T309114) Only trigger render jobs up to MAX_PAGE_RENDER_JOBS to avoid
1568 // a flood of jobs for huge files.
1569 $pageLimit = min( $this->pageCount(), self::MAX_PAGE_RENDER_JOBS );
1570
1571 $jobs[] = new ThumbnailRenderJob(
1572 $this->getTitle(),
1573 [
1574 'transformParams' => [ 'width' => $size, 'page' => 1 ],
1575 'enqueueNextPage' => true,
1576 'pageLimit' => $pageLimit
1577 ]
1578 );
1579 } elseif ( $this->isVectorized() || $this->getWidth() > $size ) {
1580 $jobs[] = new ThumbnailRenderJob(
1581 $this->getTitle(),
1582 [ 'transformParams' => [ 'width' => $size ] ]
1583 );
1584 }
1585 }
1586
1587 if ( $jobs ) {
1588 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $jobs );
1589 }
1590 }
1591
1598 protected function purgeThumbList( $dir, $files ) {
1599 $fileListDebug = strtr(
1600 var_export( $files, true ),
1601 [ "\n" => '' ]
1602 );
1603 wfDebug( __METHOD__ . ": $fileListDebug" );
1604
1605 if ( $this->repo->supportsSha1URLs() ) {
1606 $reference = $this->getSha1();
1607 } else {
1608 $reference = $this->getName();
1609 }
1610
1611 $purgeList = [];
1612 foreach ( $files as $file ) {
1613 # Check that the reference (filename or sha1) is part of the thumb name
1614 # This is a basic check to avoid erasing unrelated directories
1615 if ( str_contains( $file, $reference )
1616 || str_contains( $file, "-thumbnail" ) // "short" thumb name
1617 ) {
1618 $purgeList[] = "{$dir}/{$file}";
1619 }
1620 }
1621
1622 # Delete the thumbnails
1623 $this->repo->quickPurgeBatch( $purgeList );
1624 # Clear out the thumbnail directory if empty
1625 $this->repo->quickCleanDir( $dir );
1626 }
1627
1639 public function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
1640 if ( !$this->exists() ) {
1641 return []; // Avoid hard failure when the file does not exist. T221812
1642 }
1643
1644 $dbr = $this->repo->getReplicaDB();
1645 $oldFileQuery = FileSelectQueryBuilder::newForOldFile( $dbr )->getQueryInfo();
1646
1647 $tables = $oldFileQuery['tables'];
1648 $fields = $oldFileQuery['fields'];
1649 $join_conds = $oldFileQuery['join_conds'];
1650 $conds = $opts = [];
1651 $eq = $inc ? '=' : '';
1652 $conds[] = $dbr->expr( 'oi_name', '=', $this->title->getDBkey() );
1653
1654 if ( $start ) {
1655 $conds[] = $dbr->expr( 'oi_timestamp', "<$eq", $dbr->timestamp( $start ) );
1656 }
1657
1658 if ( $end ) {
1659 $conds[] = $dbr->expr( 'oi_timestamp', ">$eq", $dbr->timestamp( $end ) );
1660 }
1661
1662 if ( $limit ) {
1663 $opts['LIMIT'] = $limit;
1664 }
1665
1666 // Search backwards for time > x queries
1667 $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
1668 $opts['ORDER BY'] = "oi_timestamp $order";
1669 $opts['USE INDEX'] = [ 'oldimage' => 'oi_name_timestamp' ];
1670
1671 $this->getHookRunner()->onLocalFile__getHistory( $this, $tables, $fields,
1672 $conds, $opts, $join_conds );
1673
1674 $res = $dbr->newSelectQueryBuilder()
1675 ->tables( $tables )
1676 ->fields( $fields )
1677 ->conds( $conds )
1678 ->caller( __METHOD__ )
1679 ->options( $opts )
1680 ->joinConds( $join_conds )
1681 ->fetchResultSet();
1682 $r = [];
1683
1684 foreach ( $res as $row ) {
1685 $r[] = $this->repo->newFileFromRow( $row );
1686 }
1687
1688 if ( $order == 'ASC' ) {
1689 $r = array_reverse( $r ); // make sure it ends up descending
1690 }
1691
1692 return $r;
1693 }
1694
1705 public function nextHistoryLine() {
1706 if ( !$this->exists() ) {
1707 return false; // Avoid hard failure when the file does not exist. T221812
1708 }
1709
1710 # Polymorphic function name to distinguish foreign and local fetches
1711 $fname = static::class . '::' . __FUNCTION__;
1712
1713 $dbr = $this->repo->getReplicaDB();
1714
1715 if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
1716 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
1717
1718 $queryBuilder->fields( [ 'oi_archive_name' => $dbr->addQuotes( '' ), 'oi_deleted' => '0' ] )
1719 ->where( [ 'img_name' => $this->title->getDBkey() ] );
1720 $this->historyRes = $queryBuilder->caller( $fname )->fetchResultSet();
1721
1722 if ( $this->historyRes->numRows() == 0 ) {
1723 $this->historyRes = null;
1724
1725 return false;
1726 }
1727 } elseif ( $this->historyLine == 1 ) {
1728 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
1729
1730 $this->historyRes = $queryBuilder->where( [ 'oi_name' => $this->title->getDBkey() ] )
1731 ->orderBy( 'oi_timestamp', SelectQueryBuilder::SORT_DESC )
1732 ->caller( $fname )->fetchResultSet();
1733 }
1734 $this->historyLine++;
1735
1736 return $this->historyRes->fetchObject();
1737 }
1738
1743 public function resetHistory() {
1744 $this->historyLine = 0;
1745
1746 if ( $this->historyRes !== null ) {
1747 $this->historyRes = null;
1748 }
1749 }
1750
1784 public function upload( $src, $comment, $pageText, $flags = 0, $props = false,
1785 $timestamp = false, ?Authority $uploader = null, $tags = [],
1786 $createDummyRevision = true, $revert = false
1787 ) {
1788 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
1789 return $this->readOnlyFatalStatus();
1790 } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
1791 // Check this in advance to avoid writing to FileBackend and the file tables,
1792 // only to fail on insert the revision due to the text store being unavailable.
1793 return $this->readOnlyFatalStatus();
1794 }
1795
1796 $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
1797 if ( !$props ) {
1798 if ( FileRepo::isVirtualUrl( $srcPath )
1799 || FileBackend::isStoragePath( $srcPath )
1800 ) {
1801 $props = $this->repo->getFileProps( $srcPath );
1802 } else {
1803 $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
1804 $props = $mwProps->getPropsFromPath( $srcPath, true );
1805 }
1806 }
1807
1808 $options = [];
1809 $handler = MediaHandler::getHandler( $props['mime'] );
1810 if ( $handler ) {
1811 if ( is_string( $props['metadata'] ) ) {
1812 // This supports callers directly fabricating a metadata
1813 // property using serialize(). Normally the metadata property
1814 // comes from MWFileProps, in which case it won't be a string.
1815 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1816 $metadata = @unserialize( $props['metadata'] );
1817 } else {
1818 $metadata = $props['metadata'];
1819 }
1820
1821 if ( is_array( $metadata ) ) {
1822 $options['headers'] = $handler->getContentHeaders( $metadata );
1823 }
1824 } else {
1825 $options['headers'] = [];
1826 }
1827
1828 // Trim spaces on user supplied text
1829 $comment = trim( $comment );
1830
1831 $status = $this->publish( $src, $flags, $options );
1832
1833 if ( $status->successCount >= 2 ) {
1834 // There will be a copy+(one of move,copy,store).
1835 // The first succeeding does not commit us to updating the DB
1836 // since it simply copied the current version to a timestamped file name.
1837 // It is only *preferable* to avoid leaving such files orphaned.
1838 // Once the second operation goes through, then the current version was
1839 // updated and we must therefore update the DB too.
1840 $oldver = $status->value;
1841
1842 $uploadStatus = $this->recordUpload3(
1843 $oldver,
1844 $comment,
1845 $pageText,
1846 $uploader ?? RequestContext::getMain()->getAuthority(),
1847 $props,
1848 $timestamp,
1849 $tags,
1850 $createDummyRevision,
1851 $revert
1852 );
1853 if ( !$uploadStatus->isOK() ) {
1854 if ( $uploadStatus->hasMessage( 'filenotfound' ) ) {
1855 // update filenotfound error with more specific path
1856 $status->fatal( 'filenotfound', $srcPath );
1857 } else {
1858 $status->merge( $uploadStatus );
1859 }
1860 }
1861 }
1862
1863 return $status;
1864 }
1865
1882 public function recordUpload3(
1883 string $oldver,
1884 string $comment,
1885 string $pageText,
1886 Authority $performer,
1887 $props = false,
1888 $timestamp = false,
1889 $tags = [],
1890 bool $createDummyRevision = true,
1891 bool $revert = false
1892 ): Status {
1893 $dbw = $this->repo->getPrimaryDB();
1894
1895 # Imports or such might force a certain timestamp; otherwise we generate
1896 # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
1897 if ( $timestamp === false ) {
1898 $timestamp = $dbw->timestamp();
1899 $allowTimeKludge = true;
1900 } else {
1901 $allowTimeKludge = false;
1902 }
1903
1904 $props = $props ?: $this->repo->getFileProps( $this->getVirtualUrl() );
1905 $props['description'] = $comment;
1906 $props['timestamp'] = wfTimestamp( TS::MW, $timestamp ); // DB -> TS::MW
1907 $this->setProps( $props );
1908
1909 # Fail now if the file isn't there
1910 if ( !$this->fileExists ) {
1911 wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!" );
1912
1913 return Status::newFatal( 'filenotfound', $this->getRel() );
1914 }
1915
1916 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
1917 if ( !$mimeAnalyzer->isValidMajorMimeType( $this->major_mime ) ) {
1918 $this->major_mime = 'unknown';
1919 }
1920
1921 $actorNormalizaton = MediaWikiServices::getInstance()->getActorNormalization();
1922
1923 // T391473: File uploads can involve moving a lot of bytes around. Sometimes in
1924 // that time the DB connection can timeout. Normally this is automatically
1925 // reconnected, but reconnection does not work inside atomic sections.
1926 // Ping the DB to ensure it is still there prior to entering the atomic
1927 // section. TODO: Refactor upload jobs to be smarter about implicit transactions.
1928 $dbw->ping();
1929 $dbw->startAtomic( __METHOD__ );
1930
1931 $actorId = $actorNormalizaton->acquireActorId( $performer->getUser(), $dbw );
1932 $this->user = $performer->getUser();
1933
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 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
1938 $commentFields = $commentStore->insert( $dbw, 'img_description', $comment );
1939 $actorFields = [ 'img_actor' => $actorId ];
1940 $dbw->newInsertQueryBuilder()
1941 ->insertInto( 'image' )
1942 ->ignore()
1943 ->row( [
1944 'img_name' => $this->getName(),
1945 'img_size' => $this->size,
1946 'img_width' => intval( $this->width ),
1947 'img_height' => intval( $this->height ),
1948 'img_bits' => $this->bits,
1949 'img_media_type' => $this->media_type,
1950 'img_major_mime' => $this->major_mime,
1951 'img_minor_mime' => $this->minor_mime,
1952 'img_timestamp' => $dbw->timestamp( $timestamp ),
1953 'img_metadata' => $this->getMetadataForDb( $dbw ),
1954 'img_sha1' => $this->sha1
1955 ] + $commentFields + $actorFields )
1956 ->caller( __METHOD__ )->execute();
1957 $reupload = ( $dbw->affectedRows() == 0 );
1958
1959 $latestFileRevId = null;
1960 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
1961 if ( $reupload ) {
1962 $latestFileRevId = $dbw->newSelectQueryBuilder()
1963 ->select( 'fr_id' )
1964 ->from( 'filerevision' )
1965 ->where( [ 'fr_file' => $this->acquireFileIdFromName() ] )
1966 ->orderBy( 'fr_timestamp', 'DESC' )
1967 ->caller( __METHOD__ )
1968 ->fetchField();
1969 }
1970 $commentFieldsNew = $commentStore->insert( $dbw, 'fr_description', $comment );
1971 $dbw->newInsertQueryBuilder()
1972 ->insertInto( 'filerevision' )
1973 ->row( [
1974 'fr_file' => $this->acquireFileIdFromName(),
1975 'fr_size' => $this->size,
1976 'fr_width' => intval( $this->width ),
1977 'fr_height' => intval( $this->height ),
1978 'fr_bits' => $this->bits,
1979 'fr_actor' => $actorId,
1980 'fr_deleted' => 0,
1981 'fr_timestamp' => $dbw->timestamp( $timestamp ),
1982 'fr_metadata' => $this->getMetadataForDb( $dbw ),
1983 'fr_sha1' => $this->sha1
1984 ] + $commentFieldsNew )
1985 ->caller( __METHOD__ )->execute();
1986 $dbw->newUpdateQueryBuilder()
1987 ->update( 'file' )
1988 ->set( [ 'file_latest' => $dbw->insertId() ] )
1989 ->where( [ 'file_id' => $this->getFileIdFromName() ] )
1990 ->caller( __METHOD__ )->execute();
1991 }
1992
1993 if ( $reupload ) {
1994 $row = $dbw->newSelectQueryBuilder()
1995 ->select( [ 'img_timestamp', 'img_sha1' ] )
1996 ->from( 'image' )
1997 ->where( [ 'img_name' => $this->getName() ] )
1998 ->caller( __METHOD__ )->fetchRow();
1999
2000 if ( $row && $row->img_sha1 === $this->sha1 ) {
2001 $dbw->endAtomic( __METHOD__ );
2002 wfDebug( __METHOD__ . ": File " . $this->getRel() . " already exists!" );
2003 $title = Title::newFromText( $this->getName(), NS_FILE );
2004 return Status::newFatal( 'fileexists-no-change', $title->getPrefixedText() );
2005 }
2006
2007 if ( $allowTimeKludge ) {
2008 # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
2009 $lUnixtime = $row ? (int)wfTimestamp( TS::UNIX, $row->img_timestamp ) : false;
2010 # Avoid a timestamp that is not newer than the last version
2011 # TODO: the image/oldimage tables should be like page/revision with an ID field
2012 if ( $lUnixtime && (int)wfTimestamp( TS::UNIX, $timestamp ) <= $lUnixtime ) {
2013 sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
2014 $timestamp = $dbw->timestamp( $lUnixtime + 1 );
2015 $this->timestamp = wfTimestamp( TS::MW, $timestamp ); // DB -> TS::MW
2016 }
2017 }
2018
2019 $tables = [ 'image' ];
2020 $fields = [
2021 'oi_name' => 'img_name',
2022 'oi_archive_name' => $dbw->addQuotes( $oldver ),
2023 'oi_size' => 'img_size',
2024 'oi_width' => 'img_width',
2025 'oi_height' => 'img_height',
2026 'oi_bits' => 'img_bits',
2027 'oi_description_id' => 'img_description_id',
2028 'oi_timestamp' => 'img_timestamp',
2029 'oi_metadata' => 'img_metadata',
2030 'oi_media_type' => 'img_media_type',
2031 'oi_major_mime' => 'img_major_mime',
2032 'oi_minor_mime' => 'img_minor_mime',
2033 'oi_sha1' => 'img_sha1',
2034 'oi_actor' => 'img_actor',
2035 ];
2036
2037 if ( ( $this->migrationStage & SCHEMA_COMPAT_WRITE_NEW ) && $latestFileRevId && $oldver ) {
2038 $dbw->newUpdateQueryBuilder()
2039 ->update( 'filerevision' )
2040 ->set( [ 'fr_archive_name' => $oldver ] )
2041 ->where( [ 'fr_id' => $latestFileRevId ] )
2042 ->caller( __METHOD__ )->execute();
2043 }
2044
2045 if ( $this->migrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
2046 $joins = [];
2047 // (T36993) Note: $oldver can be empty here, if the previous
2048 // version of the file was broken. Allow registration of the new
2049 // version to continue anyway, because that's better than having
2050 // an image that's not fixable by user operations.
2051 // Collision, this is an update of a file
2052 // Insert previous contents into oldimage
2053 $dbw->insertSelect( 'oldimage', $tables, $fields,
2054 [ 'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
2055
2056 # Update the current image row
2057 $dbw->newUpdateQueryBuilder()
2058 ->update( 'image' )
2059 ->set( [
2060 'img_size' => $this->size,
2061 'img_width' => intval( $this->width ),
2062 'img_height' => intval( $this->height ),
2063 'img_bits' => $this->bits,
2064 'img_media_type' => $this->media_type,
2065 'img_major_mime' => $this->major_mime,
2066 'img_minor_mime' => $this->minor_mime,
2067 'img_timestamp' => $dbw->timestamp( $timestamp ),
2068 'img_metadata' => $this->getMetadataForDb( $dbw ),
2069 'img_sha1' => $this->sha1
2070 ] + $commentFields + $actorFields )
2071 ->where( [ 'img_name' => $this->getName() ] )
2072 ->caller( __METHOD__ )->execute();
2073 }
2074 }
2075
2076 $descTitle = $this->getTitle();
2077 $descId = $descTitle->getArticleID();
2078 $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $descTitle );
2079 if ( !$wikiPage instanceof WikiFilePage ) {
2080 throw new UnexpectedValueException( 'Cannot obtain instance of WikiFilePage for ' . $this->getName()
2081 . ', got instance of ' . get_class( $wikiPage ) );
2082 }
2083 $wikiPage->setFile( $this );
2084
2085 // Determine log action. If reupload is done by reverting, use a special log_action.
2086 if ( $revert ) {
2087 $logAction = 'revert';
2088 } elseif ( $reupload ) {
2089 $logAction = 'overwrite';
2090 } else {
2091 $logAction = 'upload';
2092 }
2093 // Add the log entry...
2094 $logEntry = new ManualLogEntry( 'upload', $logAction );
2095 $logEntry->setTimestamp( $this->timestamp );
2096 $logEntry->setPerformer( $performer->getUser() );
2097 $logEntry->setComment( $comment );
2098 $logEntry->setTarget( $descTitle );
2099 // Allow people using the api to associate log entries with the upload.
2100 // Log has a timestamp, but sometimes different from upload timestamp.
2101 $logEntry->setParameters(
2102 [
2103 'img_sha1' => $this->sha1,
2104 'img_timestamp' => $timestamp,
2105 ]
2106 );
2107 // Note we keep $logId around since during new image
2108 // creation, page doesn't exist yet, so log_page = 0
2109 // but we want it to point to the page we're making,
2110 // so we later modify the log entry.
2111 // For a similar reason, we avoid making an RC entry
2112 // now and wait until the page exists.
2113 $logId = $logEntry->insert();
2114
2115 if ( $descTitle->exists() ) {
2116 if ( $createDummyRevision ) {
2117 $services = MediaWikiServices::getInstance();
2118 // Use own context to get the action text in content language
2119 $formatter = $services->getLogFormatterFactory()->newFromEntry( $logEntry );
2120 $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
2121 $editSummary = $formatter->getPlainActionText();
2122
2123 $dummyRevRecord = $wikiPage->newPageUpdater( $performer->getUser() )
2124 ->setCause( PageUpdater::CAUSE_UPLOAD )
2125 ->saveDummyRevision( $editSummary, EDIT_SILENT );
2126
2127 // Associate dummy revision id
2128 $logEntry->setAssociatedRevId( $dummyRevRecord->getId() );
2129 }
2130
2131 $newPageContent = null;
2132 } else {
2133 // Make the description page and RC log entry post-commit
2134 $newPageContent = ContentHandler::makeContent( $pageText, $descTitle );
2135 }
2136
2137 // NOTE: Even after ending this atomic section, we are probably still in the implicit
2138 // transaction started by any prior master query in the request. We cannot yet safely
2139 // schedule jobs, see T263301.
2140 $dbw->endAtomic( __METHOD__ );
2141 $fname = __METHOD__;
2142
2143 # Do some cache purges after final commit so that:
2144 # a) Changes are more likely to be seen post-purge
2145 # b) They won't cause rollback of the log publish/update above
2146 $purgeUpdate = new AutoCommitUpdate(
2147 $dbw,
2148 __METHOD__,
2149 function () use (
2150 $reupload, $wikiPage, $newPageContent, $comment, $performer,
2151 $logEntry, $logId, $descId, $tags, $fname
2152 ) {
2153 # Update memcache after the commit
2154 $this->invalidateCache();
2155
2156 $updateLogPage = false;
2157 if ( $newPageContent ) {
2158 # New file page; create the description page.
2159 # There's already a log entry, so don't make a second RC entry
2160 # CDN and file cache for the description page are purged by doUserEditContent.
2161 $revRecord = $wikiPage->newPageUpdater( $performer )
2162 ->setCause( PageUpdater::CAUSE_UPLOAD )
2163 ->setContent( SlotRecord::MAIN, $newPageContent )
2164 ->saveRevision( $comment, EDIT_NEW | EDIT_SUPPRESS_RC );
2165
2166 if ( $revRecord ) {
2167 // Associate new page revision id
2168 $logEntry->setAssociatedRevId( $revRecord->getId() );
2169
2170 // This relies on the resetArticleID() call in WikiPage::insertOn(),
2171 // which is triggered on $descTitle by doUserEditContent() above.
2172 $updateLogPage = $revRecord->getPageId();
2173 }
2174 } else {
2175 # Existing file page: invalidate description page cache
2176 $title = $wikiPage->getTitle();
2177 $title->invalidateCache();
2178 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2179 $hcu->purgeTitleUrls( $title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2180 # Allow the new file version to be patrolled from the page footer
2181 Article::purgePatrolFooterCache( $descId );
2182 }
2183
2184 # Update associated rev id. This should be done by $logEntry->insert() earlier,
2185 # but setAssociatedRevId() wasn't called at that point yet...
2186 $logParams = $logEntry->getParameters();
2187 $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
2188 $update = [ 'log_params' => LogEntryBase::makeParamBlob( $logParams ) ];
2189 if ( $updateLogPage ) {
2190 # Also log page, in case where we just created it above
2191 $update['log_page'] = $updateLogPage;
2192 }
2193 $this->getRepo()->getPrimaryDB()->newUpdateQueryBuilder()
2194 ->update( 'logging' )
2195 ->set( $update )
2196 ->where( [ 'log_id' => $logId ] )
2197 ->caller( $fname )->execute();
2198
2199 $this->getRepo()->getPrimaryDB()->newInsertQueryBuilder()
2200 ->insertInto( 'log_search' )
2201 ->row( [
2202 'ls_field' => 'associated_rev_id',
2203 'ls_value' => (string)$logEntry->getAssociatedRevId(),
2204 'ls_log_id' => $logId,
2205 ] )
2206 ->caller( $fname )->execute();
2207
2208 # Add change tags, if any
2209 if ( $tags ) {
2210 $logEntry->addTags( $tags );
2211 }
2212
2213 # Uploads can be patrolled
2214 $logEntry->setIsPatrollable( true );
2215
2216 # Now that the log entry is up-to-date, make an RC entry.
2217 $logEntry->publish( $logId );
2218
2219 # Run hook for other updates (typically more cache purging)
2220 $this->getHookRunner()->onFileUpload( $this, $reupload, !$newPageContent );
2221
2222 if ( $reupload ) {
2223 # Delete old thumbnails
2224 $this->purgeThumbnails();
2225 # Remove the old file from the CDN cache
2226 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2227 $hcu->purgeUrls( $this->getUrl(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2228 } else {
2229 # Update backlink pages pointing to this title if created
2230 $blcFactory = MediaWikiServices::getInstance()->getBacklinkCacheFactory();
2231 LinksUpdate::queueRecursiveJobsForTable(
2232 $this->getTitle(),
2233 'imagelinks',
2234 'upload-image',
2235 $performer->getUser()->getName(),
2236 $blcFactory->getBacklinkCache( $this->getTitle() )
2237 );
2238 }
2239
2240 $this->prerenderThumbnails();
2241 }
2242 );
2243
2244 # Invalidate cache for all pages using this file
2245 $cacheUpdateJob = HTMLCacheUpdateJob::newForBacklinks(
2246 $this->getTitle(),
2247 'imagelinks',
2248 [ 'causeAction' => 'file-upload', 'causeAgent' => $performer->getUser()->getName() ]
2249 );
2250
2251 // NOTE: We are probably still in the implicit transaction started by DBO_TRX. We should
2252 // only schedule jobs after that transaction was committed, so a job queue failure
2253 // doesn't cause the upload to fail (T263301). Also, we should generally not schedule any
2254 // Jobs or the DeferredUpdates that assume the update is complete until after the
2255 // transaction has been committed and we are sure that the upload was indeed successful.
2256 $dbw->onTransactionCommitOrIdle( static function () use ( $reupload, $purgeUpdate, $cacheUpdateJob ) {
2257 DeferredUpdates::addUpdate( $purgeUpdate, DeferredUpdates::PRESEND );
2258
2259 if ( !$reupload ) {
2260 // This is a new file, so update the image count
2261 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => 1 ] ) );
2262 }
2263
2264 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush( $cacheUpdateJob );
2265 }, __METHOD__ );
2266
2267 return Status::newGood();
2268 }
2269
2286 public function publish( $src, $flags = 0, array $options = [] ) {
2287 return $this->publishTo( $src, $this->getRel(), $flags, $options );
2288 }
2289
2306 protected function publishTo( $src, $dstRel, $flags = 0, array $options = [] ) {
2307 $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
2308
2309 $repo = $this->getRepo();
2310 if ( $repo->getReadOnlyReason() !== false ) {
2311 return $this->readOnlyFatalStatus();
2312 }
2313
2314 $status = $this->acquireFileLock();
2315 if ( !$status->isOK() ) {
2316 return $status;
2317 }
2318
2319 if ( $this->isOld() ) {
2320 $archiveRel = $dstRel;
2321 $archiveName = basename( $archiveRel );
2322 } else {
2323 $archiveName = ConvertibleTimestamp::now( TS::MW ) . '!' . $this->getName();
2324 $archiveRel = $this->getArchiveRel( $archiveName );
2325 }
2326
2327 if ( $repo->hasSha1Storage() ) {
2328 $sha1 = FileRepo::isVirtualUrl( $srcPath )
2329 ? $repo->getFileSha1( $srcPath )
2330 : FSFile::getSha1Base36FromPath( $srcPath );
2332 $wrapperBackend = $repo->getBackend();
2333 '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
2334 $dst = $wrapperBackend->getPathForSHA1( $sha1 );
2335 $status = $repo->quickImport( $src, $dst );
2336 if ( $flags & File::DELETE_SOURCE ) {
2337 unlink( $srcPath );
2338 }
2339
2340 if ( $this->exists() ) {
2341 $status->value = $archiveName;
2342 }
2343 } else {
2344 $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
2345 $status = $repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options );
2346
2347 if ( $status->value == 'new' ) {
2348 $status->value = '';
2349 } else {
2350 $status->value = $archiveName;
2351 }
2352 }
2353
2354 $this->releaseFileLock();
2355 return $status;
2356 }
2357
2376 public function move( $target ) {
2377 $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
2378 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2379 return $this->readOnlyFatalStatus();
2380 }
2381
2382 wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
2383 $batch = new LocalFileMoveBatch( $this, $target );
2384
2385 $status = $batch->addCurrent();
2386 if ( !$status->isOK() ) {
2387 return $status;
2388 }
2389 $archiveNames = $batch->addOlds();
2390 $status = $batch->execute();
2391
2392 wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
2393
2394 // Purge the source and target files outside the transaction...
2395 $oldTitleFile = $localRepo->newFile( $this->title );
2396 $newTitleFile = $localRepo->newFile( $target );
2397 DeferredUpdates::addUpdate(
2398 new AutoCommitUpdate(
2399 $this->getRepo()->getPrimaryDB(),
2400 __METHOD__,
2401 static function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
2402 $oldTitleFile->purgeEverything();
2403 foreach ( $archiveNames as $archiveName ) {
2405 '@phan-var OldLocalFile $oldTitleFile';
2406 $oldTitleFile->purgeOldThumbnails( $archiveName );
2407 }
2408 $newTitleFile->purgeEverything();
2409 }
2410 ),
2411 DeferredUpdates::PRESEND
2412 );
2413
2414 if ( $status->isOK() ) {
2415 // Now switch the object
2416 $this->title = $target;
2417 // Force regeneration of the name and hashpath
2418 $this->name = null;
2419 $this->hashPath = null;
2420 }
2421
2422 return $status;
2423 }
2424
2441 public function deleteFile( $reason, UserIdentity $user, $suppress = false ) {
2442 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2443 return $this->readOnlyFatalStatus();
2444 }
2445
2446 $batch = new LocalFileDeleteBatch( $this, $user, $reason, $suppress );
2447
2448 $batch->addCurrent();
2449 // Get old version relative paths
2450 $archiveNames = $batch->addOlds();
2451 $status = $batch->execute();
2452
2453 if ( $status->isOK() ) {
2454 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'images' => -1 ] ) );
2455 }
2456
2457 // To avoid slow purges in the transaction, move them outside...
2458 DeferredUpdates::addUpdate(
2459 new AutoCommitUpdate(
2460 $this->getRepo()->getPrimaryDB(),
2461 __METHOD__,
2462 function () use ( $archiveNames ) {
2463 $this->purgeEverything();
2464 foreach ( $archiveNames as $archiveName ) {
2465 $this->purgeOldThumbnails( $archiveName );
2466 }
2467 }
2468 ),
2469 DeferredUpdates::PRESEND
2470 );
2471
2472 // Purge the CDN
2473 $purgeUrls = [];
2474 foreach ( $archiveNames as $archiveName ) {
2475 $purgeUrls[] = $this->getArchiveUrl( $archiveName );
2476 }
2477
2478 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2479 $hcu->purgeUrls( $purgeUrls, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2480
2481 return $status;
2482 }
2483
2501 public function deleteOldFile( $archiveName, $reason, UserIdentity $user, $suppress = false ) {
2502 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2503 return $this->readOnlyFatalStatus();
2504 }
2505
2506 $batch = new LocalFileDeleteBatch( $this, $user, $reason, $suppress );
2507
2508 $batch->addOld( $archiveName );
2509 $status = $batch->execute();
2510
2511 $this->purgeOldThumbnails( $archiveName );
2512 if ( $status->isOK() ) {
2513 $this->purgeDescription();
2514 }
2515
2516 $url = $this->getArchiveUrl( $archiveName );
2517 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2518 $hcu->purgeUrls( $url, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2519
2520 return $status;
2521 }
2522
2535 public function restore( $versions = [], $unsuppress = false ) {
2536 if ( $this->getRepo()->getReadOnlyReason() !== false ) {
2537 return $this->readOnlyFatalStatus();
2538 }
2539
2540 $batch = new LocalFileRestoreBatch( $this, $unsuppress );
2541
2542 if ( !$versions ) {
2543 $batch->addAll();
2544 } else {
2545 $batch->addIds( $versions );
2546 }
2547 $status = $batch->execute();
2548 if ( $status->isGood() ) {
2549 $cleanupStatus = $batch->cleanup();
2550 $cleanupStatus->successCount = 0;
2551 $cleanupStatus->failCount = 0;
2552 $status->merge( $cleanupStatus );
2553 }
2554
2555 return $status;
2556 }
2557
2568 public function getDescriptionUrl() {
2569 // Avoid hard failure when the file does not exist. T221812
2570 return $this->title ? $this->title->getLocalURL() : false;
2571 }
2572
2583 public function getDescriptionText( ?Language $lang = null ) {
2584 if ( !$lang ) {
2585 wfDeprecatedMsg( 'Calling File::getDescriptionText without a lang parameter ' .
2586 'was deprecated in MediaWiki 1.46', '1.46' );
2587 }
2588 if ( !$this->title ) {
2589 return false; // Avoid hard failure when the file does not exist. T221812
2590 }
2591
2592 $services = MediaWikiServices::getInstance();
2593 $page = $services->getPageStore()->getPageByReference( $this->getTitle() );
2594 if ( !$page ) {
2595 return false;
2596 }
2597
2598 if ( $lang ) {
2599 $parserOptions = ParserOptions::newFromUserAndLang(
2600 RequestContext::getMain()->getUser(),
2601 $lang
2602 );
2603 } else {
2604 $parserOptions = ParserOptions::newFromContext( RequestContext::getMain() );
2605 }
2606
2607 $parseStatus = $services->getParserOutputAccess()
2608 ->getParserOutput( $page, $parserOptions );
2609
2610 if ( !$parseStatus->isGood() ) {
2611 // Rendering failed.
2612 return false;
2613 }
2614 // TODO T371004 move runOutputPipeline out of $parserOutput
2615 return $parseStatus->getValue()->runOutputPipeline( $parserOptions, [] )->getContentHolderText();
2616 }
2617
2625 public function getUploader( int $audience = self::FOR_PUBLIC, ?Authority $performer = null ): ?UserIdentity {
2626 $this->load();
2627 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
2628 return null;
2629 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $performer ) ) {
2630 return null;
2631 } else {
2632 return $this->user;
2633 }
2634 }
2635
2642 public function getDescription( $audience = self::FOR_PUBLIC, ?Authority $performer = null ) {
2643 $this->load();
2644 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
2645 return '';
2646 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $performer ) ) {
2647 return '';
2648 } else {
2649 return $this->description;
2650 }
2651 }
2652
2657 public function getTimestamp() {
2658 $this->load();
2659
2660 return $this->timestamp;
2661 }
2662
2667 public function getDescriptionTouched() {
2668 if ( !$this->exists() ) {
2669 return false; // Avoid hard failure when the file does not exist. T221812
2670 }
2671
2672 // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
2673 // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
2674 // need to differentiate between null (uninitialized) and false (failed to load).
2675 if ( $this->descriptionTouched === null ) {
2676 $touched = $this->repo->getReplicaDB()->newSelectQueryBuilder()
2677 ->select( 'page_touched' )
2678 ->from( 'page' )
2679 ->where( [ 'page_namespace' => $this->title->getNamespace() ] )
2680 ->andWhere( [ 'page_title' => $this->title->getDBkey() ] )
2681 ->caller( __METHOD__ )->fetchField();
2682 $this->descriptionTouched = $touched ? wfTimestamp( TS::MW, $touched ) : false;
2683 }
2684
2685 return $this->descriptionTouched;
2686 }
2687
2692 public function getSha1() {
2693 $this->load();
2694 return $this->sha1;
2695 }
2696
2700 public function isCacheable() {
2701 $this->load();
2702
2703 // If extra data (metadata) was not loaded then it must have been large
2704 return $this->extraDataLoaded
2705 && strlen( serialize( $this->metadataArray ) ) <= self::CACHE_FIELD_MAX_LEN;
2706 }
2707
2716 public function acquireFileLock( $timeout = 0 ) {
2717 return Status::wrap( $this->getRepo()->getBackend()->lockFiles(
2718 [ $this->getPath() ], LockManager::LOCK_EX, $timeout
2719 ) );
2720 }
2721
2728 public function releaseFileLock() {
2729 return Status::wrap( $this->getRepo()->getBackend()->unlockFiles(
2730 [ $this->getPath() ], LockManager::LOCK_EX
2731 ) );
2732 }
2733
2744 public function lock() {
2745 if ( !$this->locked ) {
2746 $logger = LoggerFactory::getInstance( 'LocalFile' );
2747
2748 $dbw = $this->repo->getPrimaryDB();
2749 $makesTransaction = !$dbw->trxLevel();
2750 $dbw->startAtomic( self::ATOMIC_SECTION_LOCK );
2751 // T56736: use simple lock to handle when the file does not exist.
2752 // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
2753 // Also, that would cause contention on INSERT of similarly named rows.
2754 $status = $this->acquireFileLock( 10 ); // represents all versions of the file
2755 if ( !$status->isGood() ) {
2756 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2757 $logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
2758
2759 throw new LocalFileLockError( $status );
2760 }
2761 // Release the lock *after* commit to avoid row-level contention.
2762 // Make sure it triggers on rollback() as well as commit() (T132921).
2763 $dbw->onTransactionResolution(
2764 function () use ( $logger ) {
2765 $status = $this->releaseFileLock();
2766 if ( !$status->isGood() ) {
2767 $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
2768 }
2769 },
2770 __METHOD__
2771 );
2772 // Callers might care if the SELECT snapshot is safely fresh
2773 $this->lockedOwnTrx = $makesTransaction;
2774 }
2775
2776 $this->locked++;
2777
2778 return $this->lockedOwnTrx;
2779 }
2780
2791 public function unlock() {
2792 if ( $this->locked ) {
2793 --$this->locked;
2794 if ( !$this->locked ) {
2795 $dbw = $this->repo->getPrimaryDB();
2796 $dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
2797 $this->lockedOwnTrx = false;
2798 }
2799 }
2800 }
2801
2805 protected function readOnlyFatalStatus() {
2806 return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
2807 $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
2808 }
2809
2813 public function __destruct() {
2814 $this->unlock();
2815 }
2816}
2817
2819class_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:69
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:81
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_')
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.
getDescriptionText(?Language $lang=null)
Get the HTML text of the description page This is not used by ImagePage for local files,...
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 write to the file backend.
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:45
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:65
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'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], '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, 'PHPSessionHandling'=> 'warn', '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',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, '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, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, '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: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
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...