MediaWiki master
WikiPage.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Page;
8
9use BadMethodCallException;
10use InvalidArgumentException;
17use MediaWiki\DAO\WikiAwareEntityTrait;
22use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
56use RuntimeException;
57use stdClass;
58use Stringable;
59use Wikimedia\Assert\Assert;
60use Wikimedia\Assert\PreconditionException;
61use Wikimedia\NonSerializable\NonSerializableTrait;
68use Wikimedia\Timestamp\TimestampFormat as TS;
69
82class WikiPage implements Stringable, Page, PageRecord {
83 use NonSerializableTrait;
84 use ProtectedHookAccessorTrait;
85 use WikiAwareEntityTrait;
86
87 // Constants for $mDataLoadedFrom and related
88
93 protected $mTitle;
94
99 protected $mDataLoaded = false;
100
105 private $mPageIsRedirectField = false;
106
110 private $mIsNew = false;
111
116 protected $mLatest = false;
117
122 protected $mPreparedEdit = false;
123
127 protected $mId = null;
128
132 protected $mDataLoadedFrom = IDBAccessObject::READ_NONE;
133
137 private $mLastRevision = null;
138
142 protected $mTimestamp = '';
143
147 protected $mTouched = '19700101000000';
148
152 protected $mLanguage = null;
153
157 protected $mLinksUpdated = '19700101000000';
158
162 private $derivedDataUpdater = null;
163
164 public function __construct( PageIdentity $pageIdentity ) {
165 $pageIdentity->assertWiki( PageIdentity::LOCAL );
166
167 // TODO: remove the need for casting to Title.
168 $title = Title::newFromPageIdentity( $pageIdentity );
169 if ( !$title->canExist() ) {
170 throw new InvalidArgumentException( "WikiPage constructed on a Title that cannot exist as a page: $title" );
171 }
172
173 $this->mTitle = $title;
174 }
175
180 public function __clone() {
181 $this->mTitle = clone $this->mTitle;
182 }
183
190 public static function convertSelectType( $type ) {
191 switch ( $type ) {
192 case 'fromdb':
193 return IDBAccessObject::READ_NORMAL;
194 case 'fromdbmaster':
195 return IDBAccessObject::READ_LATEST;
196 case 'forupdate':
197 return IDBAccessObject::READ_LOCKING;
198 default:
199 // It may already be an integer or whatever else
200 return $type;
201 }
202 }
203
204 private function getPageUpdaterFactory(): PageUpdaterFactory {
205 return MediaWikiServices::getInstance()->getPageUpdaterFactory();
206 }
207
211 private function getRevisionStore() {
213 }
214
218 private function getDBLoadBalancer() {
219 return MediaWikiServices::getInstance()->getDBLoadBalancer();
220 }
221
228 public function getActionOverrides() {
229 return $this->getContentHandler()->getActionOverrides();
230 }
231
241 public function getContentHandler() {
242 $factory = MediaWikiServices::getInstance()->getContentHandlerFactory();
243 return $factory->getContentHandler( $this->getContentModel() );
244 }
245
250 public function getTitle(): Title {
251 return $this->mTitle;
252 }
253
258 public function clear() {
259 $this->mDataLoaded = false;
260 $this->mDataLoadedFrom = IDBAccessObject::READ_NONE;
261
262 $this->clearCacheFields();
263 }
264
269 protected function clearCacheFields() {
270 $this->mId = null;
271 $this->mPageIsRedirectField = false;
272 $this->mLastRevision = null; // Latest revision
273 $this->mTouched = '19700101000000';
274 $this->mLanguage = null;
275 $this->mLinksUpdated = '19700101000000';
276 $this->mTimestamp = '';
277 $this->mIsNew = false;
278 $this->mLatest = false;
279 // T59026: do not clear $this->derivedDataUpdater since getDerivedDataUpdater() already
280 // checks the requested rev ID and content against the cached one. For most
281 // content types, the output should not change during the lifetime of this cache.
282 // Clearing it can cause extra parses on edit for no reason.
283 }
284
290 public function clearPreparedEdit() {
291 $this->mPreparedEdit = false;
292 }
293
307 public static function getQueryInfo() {
308 $pageLanguageUseDB = MediaWikiServices::getInstance()->getMainConfig()->get(
309 MainConfigNames::PageLanguageUseDB );
310
311 $ret = [
312 'tables' => [ 'page' ],
313 'fields' => [
314 'page_id',
315 'page_namespace',
316 'page_title',
317 'page_is_redirect',
318 'page_is_new',
319 'page_random',
320 'page_touched',
321 'page_links_updated',
322 'page_latest',
323 'page_len',
324 'page_content_model',
325 ],
326 'joins' => [],
327 ];
328
329 if ( $pageLanguageUseDB ) {
330 $ret['fields'][] = 'page_lang';
331 }
332
333 return $ret;
334 }
335
343 protected function pageData( $dbr, $conditions, $options = [] ) {
344 $pageQuery = self::getQueryInfo();
345
346 $this->getHookRunner()->onArticlePageDataBefore(
347 $this, $pageQuery['fields'], $pageQuery['tables'], $pageQuery['joins'] );
348
349 $row = $dbr->newSelectQueryBuilder()
350 ->queryInfo( $pageQuery )
351 ->where( $conditions )
352 ->caller( __METHOD__ )
353 ->options( $options )
354 ->fetchRow();
355
356 $this->getHookRunner()->onArticlePageDataAfter( $this, $row );
357
358 return $row;
359 }
360
370 public function pageDataFromTitle( $dbr, $title, $recency = IDBAccessObject::READ_NORMAL ) {
371 if ( !$title->canExist() ) {
372 return false;
373 }
374 $options = [];
375 if ( ( $recency & IDBAccessObject::READ_EXCLUSIVE ) == IDBAccessObject::READ_EXCLUSIVE ) {
376 $options[] = 'FOR UPDATE';
377 } elseif ( ( $recency & IDBAccessObject::READ_LOCKING ) == IDBAccessObject::READ_LOCKING ) {
378 $options[] = 'LOCK IN SHARE MODE';
379 }
380
381 return $this->pageData( $dbr, [
382 'page_namespace' => $title->getNamespace(),
383 'page_title' => $title->getDBkey() ], $options );
384 }
385
394 public function pageDataFromId( $dbr, $id, $options = [] ) {
395 return $this->pageData( $dbr, [ 'page_id' => $id ], $options );
396 }
397
410 public function loadPageData( $from = 'fromdb' ) {
411 $from = self::convertSelectType( $from );
412 if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
413 // We already have the data from the correct location, no need to load it twice.
414 return;
415 }
416
417 if ( is_int( $from ) ) {
418 $loadBalancer = $this->getDBLoadBalancer();
419 if ( ( $from & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST ) {
420 $index = DB_PRIMARY;
421 } else {
422 $index = DB_REPLICA;
423 }
424 $db = $loadBalancer->getConnection( $index );
425 $data = $this->pageDataFromTitle( $db, $this->mTitle, $from );
426
427 if ( !$data
428 && $index == DB_REPLICA
429 && $loadBalancer->hasReplicaServers()
430 && $loadBalancer->hasOrMadeRecentPrimaryChanges()
431 ) {
432 $from = IDBAccessObject::READ_LATEST;
433 $db = $loadBalancer->getConnection( DB_PRIMARY );
434 $data = $this->pageDataFromTitle( $db, $this->mTitle, $from );
435 }
436 } else {
437 // No idea from where the caller got this data, assume replica DB.
438 $data = $from;
439 $from = IDBAccessObject::READ_NORMAL;
440 }
441
442 $this->loadFromRow( $data, $from );
443 }
444
458 public function wasLoadedFrom( $from ) {
459 $from = self::convertSelectType( $from );
460
461 if ( !is_int( $from ) ) {
462 // No idea from where the caller got this data, assume replica DB.
463 $from = IDBAccessObject::READ_NORMAL;
464 }
465
466 if ( $from <= $this->mDataLoadedFrom ) {
467 return true;
468 }
469
470 return false;
471 }
472
484 public function loadFromRow( $data, $from ) {
485 $lc = MediaWikiServices::getInstance()->getLinkCache();
486 $lc->clearLink( $this->mTitle );
487
488 if ( $data ) {
489 $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
490
491 $this->mTitle->loadFromRow( $data );
492 $this->mId = intval( $data->page_id );
493 $this->mTouched = MWTimestamp::convert( TS::MW, $data->page_touched );
494 $this->mLanguage = $data->page_lang ?? null;
495 $this->mLinksUpdated = $data->page_links_updated === null
496 ? null
497 : MWTimestamp::convert( TS::MW, $data->page_links_updated );
498 $this->mPageIsRedirectField = (bool)$data->page_is_redirect;
499 $this->mIsNew = (bool)( $data->page_is_new ?? 0 );
500 $this->mLatest = intval( $data->page_latest );
501 // T39225: $latest may no longer match the cached latest RevisionRecord object.
502 // Double-check the ID of any cached latest RevisionRecord object for consistency.
503 if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
504 $this->mLastRevision = null;
505 $this->mTimestamp = '';
506 }
507 } else {
508 $lc->addBadLinkObj( $this->mTitle );
509
510 $this->mTitle->loadFromRow( false );
511
512 $this->clearCacheFields();
513
514 $this->mId = 0;
515 }
516
517 $this->mDataLoaded = true;
518 $this->mDataLoadedFrom = self::convertSelectType( $from );
519 }
520
526 public function getId( $wikiId = self::LOCAL ): int {
527 $this->assertWiki( $wikiId );
528
529 if ( !$this->mDataLoaded ) {
530 $this->loadPageData();
531 }
532 return $this->mId;
533 }
534
538 public function exists(): bool {
539 if ( !$this->mDataLoaded ) {
540 $this->loadPageData();
541 }
542 return $this->mId > 0;
543 }
544
553 public function hasViewableContent() {
554 return $this->mTitle->isKnown();
555 }
556
563 public function isRedirect() {
564 $this->loadPageData();
565 if ( $this->mPageIsRedirectField ) {
566 return MediaWikiServices::getInstance()->getRedirectLookup()
567 ->getRedirectTarget( $this->getTitle() ) !== null;
568 }
569
570 return false;
571 }
572
581 public function isNew() {
582 if ( !$this->mDataLoaded ) {
583 $this->loadPageData();
584 }
585
586 return $this->mIsNew;
587 }
588
599 public function getContentModel() {
600 if ( $this->exists() ) {
601 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
602
603 return $cache->getWithSetCallback(
604 $cache->makeKey( 'page-content-model', $this->getLatest() ),
605 $cache::TTL_MONTH,
606 function () {
607 $rev = $this->getRevisionRecord();
608 if ( $rev ) {
609 // Look at the revision's actual content model
610 $slot = $rev->getSlot(
611 SlotRecord::MAIN,
612 RevisionRecord::RAW
613 );
614 return $slot->getModel();
615 } else {
616 LoggerFactory::getInstance( 'wikipage' )->warning(
617 'Page exists but has no (visible) revisions!',
618 [
619 'page-title' => $this->mTitle->getPrefixedDBkey(),
620 'page-id' => $this->getId(),
621 ]
622 );
623 return $this->mTitle->getContentModel();
624 }
625 },
626 [ 'pcTTL' => $cache::TTL_PROC_LONG ]
627 );
628 }
629
630 // use the default model for this page
631 return $this->mTitle->getContentModel();
632 }
633
638 public function checkTouched() {
639 return ( $this->exists() && !$this->isRedirect() );
640 }
641
646 public function getTouched() {
647 if ( !$this->mDataLoaded ) {
648 $this->loadPageData();
649 }
650 return $this->mTouched;
651 }
652
656 public function getLanguage() {
657 if ( !$this->mDataLoaded ) {
658 $this->loadLastEdit();
659 }
660
661 return $this->mLanguage;
662 }
663
668 public function getLinksTimestamp() {
669 if ( !$this->mDataLoaded ) {
670 $this->loadPageData();
671 }
672 return $this->mLinksUpdated;
673 }
674
680 public function getLatest( $wikiId = self::LOCAL ) {
681 $this->assertWiki( $wikiId );
682
683 if ( !$this->mDataLoaded ) {
684 $this->loadPageData();
685 }
686 return (int)$this->mLatest;
687 }
688
693 protected function loadLastEdit() {
694 if ( $this->mLastRevision !== null ) {
695 return; // already loaded
696 }
697
698 $latest = $this->getLatest();
699 if ( !$latest ) {
700 return; // page doesn't exist or is missing page_latest info
701 }
702
703 if ( $this->mDataLoadedFrom == IDBAccessObject::READ_LOCKING ) {
704 // T39225: if session S1 loads the page row FOR UPDATE, the result always
705 // includes the latest changes committed. This is true even within REPEATABLE-READ
706 // transactions, where S1 normally only sees changes committed before the first S1
707 // SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
708 // may not find it since a page row UPDATE and revision row INSERT by S2 may have
709 // happened after the first S1 SELECT.
710 // https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html#isolevel_repeatable-read
711 $revision = $this->getRevisionStore()
712 ->getRevisionByPageId( $this->getId(), $latest, IDBAccessObject::READ_LOCKING );
713 } elseif ( $this->mDataLoadedFrom == IDBAccessObject::READ_LATEST ) {
714 // Bug T93976: if page_latest was loaded from the primary DB, fetch the
715 // revision from there as well, as it may not exist yet on a replica DB.
716 // Also, this keeps the queries in the same REPEATABLE-READ snapshot.
717 $revision = $this->getRevisionStore()
718 ->getRevisionByPageId( $this->getId(), $latest, IDBAccessObject::READ_LATEST );
719 } else {
720 $revision = $this->getRevisionStore()->getKnownCurrentRevision( $this->getTitle(), $latest );
721 }
722
723 if ( $revision ) {
724 $this->setLastEdit( $revision );
725 }
726 }
727
731 private function setLastEdit( RevisionRecord $revRecord ) {
732 $this->mLastRevision = $revRecord;
733 $this->mLatest = $revRecord->getId();
734 $this->mTimestamp = $revRecord->getTimestamp();
735 $this->mTouched = max( $this->mTouched, $revRecord->getTimestamp() );
736 }
737
743 public function getRevisionRecord() {
744 $this->loadLastEdit();
745 return $this->mLastRevision;
746 }
747
761 public function getContent( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
762 $this->loadLastEdit();
763 if ( $this->mLastRevision ) {
764 return $this->mLastRevision->getContent( SlotRecord::MAIN, $audience, $performer );
765 }
766 return null;
767 }
768
772 public function getTimestamp() {
773 // Check if the field has been filled by WikiPage::setTimestamp()
774 if ( !$this->mTimestamp ) {
775 $this->loadLastEdit();
776 }
777
778 return MWTimestamp::convert( TS::MW, $this->mTimestamp );
779 }
780
786 public function setTimestamp( $ts ) {
787 $this->mTimestamp = MWTimestamp::convert( TS::MW, $ts );
788 }
789
800 public function getUser( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
801 $this->loadLastEdit();
802 if ( $this->mLastRevision ) {
803 $revUser = $this->mLastRevision->getUser( $audience, $performer );
804 return $revUser ? $revUser->getId() : 0;
805 } else {
806 return -1;
807 }
808 }
809
821 public function getCreator( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
822 $revRecord = $this->getRevisionStore()->getFirstRevision( $this->getTitle() );
823 if ( $revRecord ) {
824 return $revRecord->getUser( $audience, $performer );
825 } else {
826 return null;
827 }
828 }
829
840 public function getUserText( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
841 $this->loadLastEdit();
842 if ( $this->mLastRevision ) {
843 $revUser = $this->mLastRevision->getUser( $audience, $performer );
844 return $revUser ? $revUser->getName() : '';
845 } else {
846 return '';
847 }
848 }
849
861 public function getComment( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
862 $this->loadLastEdit();
863 if ( $this->mLastRevision ) {
864 $revComment = $this->mLastRevision->getComment( $audience, $performer );
865 return $revComment ? $revComment->text : '';
866 } else {
867 return '';
868 }
869 }
870
876 public function getMinorEdit() {
877 $this->loadLastEdit();
878 if ( $this->mLastRevision ) {
879 return $this->mLastRevision->isMinor();
880 } else {
881 return false;
882 }
883 }
884
901 public function isCountable( $editInfo = false ) {
902 $mwServices = MediaWikiServices::getInstance();
903 $articleCountMethod = $mwServices->getMainConfig()->get( MainConfigNames::ArticleCountMethod );
904
905 // NOTE: Keep in sync with DerivedPageDataUpdater::isCountable.
906
907 if ( !$this->mTitle->isContentPage() ) {
908 return false;
909 }
910
911 if ( $editInfo instanceof PreparedEdit ) {
912 // NOTE: only the main slot can make a page a redirect
913 $content = $editInfo->pstContent;
914 } elseif ( $editInfo instanceof PreparedUpdate ) {
915 // NOTE: only the main slot can make a page a redirect
916 $content = $editInfo->getRawContent( SlotRecord::MAIN );
917 } else {
918 $content = $this->getContent();
919 }
920
921 if ( !$content || $content->isRedirect() ) {
922 return false;
923 }
924
925 $hasLinks = null;
926
927 if ( $articleCountMethod === 'link' ) {
928 // nasty special case to avoid re-parsing to detect links
929
930 if ( $editInfo ) {
931 $hasLinks = $editInfo->output->hasLinks();
932 } else {
933 // NOTE: keep in sync with RevisionRenderer::getLinkCount
934 // NOTE: keep in sync with DerivedPageDataUpdater::isCountable
935 $dbr = $mwServices
936 ->getConnectionProvider()
937 ->getReplicaDatabase( PageLinksTable::VIRTUAL_DOMAIN );
938 $hasLinks = (bool)$dbr->newSelectQueryBuilder()
939 ->select( '1' )
940 ->from( 'pagelinks' )
941 ->where( [ 'pl_from' => $this->getId() ] )
942 ->caller( __METHOD__ )->fetchField();
943 }
944 }
945
946 // TODO: MCR: determine $hasLinks for each slot, and use that info
947 // with that slot's Content's isCountable method. That requires per-
948 // slot ParserOutput in the ParserCache, or per-slot info in the
949 // pagelinks table.
950 return $content->isCountable( $hasLinks );
951 }
952
962 public function getRedirectTarget() {
963 $target = MediaWikiServices::getInstance()->getRedirectLookup()->getRedirectTarget( $this );
964 return Title::castFromLinkTarget( $target );
965 }
966
974 public function insertRedirectEntry( LinkTarget $rt, $oldLatest = null ) {
975 return MediaWikiServices::getInstance()->getRedirectStore()
976 ->updateRedirectTarget( $this, $rt );
977 }
978
984 public function followRedirect() {
985 return $this->getRedirectURL( $this->getRedirectTarget() );
986 }
987
995 public function getRedirectURL( $rt ) {
996 if ( !$rt ) {
997 return false;
998 }
999
1000 if ( $rt->isExternal() ) {
1001 if ( $rt->isLocal() ) {
1002 // Offsite wikis need an HTTP redirect.
1003 // This can be hard to reverse and may produce loops,
1004 // so they may be disabled in the site configuration.
1005 $source = $this->mTitle->getFullURL( 'redirect=no' );
1006 return $rt->getFullURL( [ 'rdfrom' => $source ] );
1007 } else {
1008 // External pages without "local" bit set are not valid
1009 // redirect targets
1010 return false;
1011 }
1012 }
1013
1014 if ( $rt->isSpecialPage() ) {
1015 // Gotta handle redirects to special pages differently:
1016 // Fill the HTTP response "Location" header and ignore the rest of the page we're on.
1017 // Some pages are not valid targets.
1018 if ( $rt->isValidRedirectTarget() ) {
1019 return $rt->getFullURL();
1020 } else {
1021 return false;
1022 }
1023 } elseif ( !$rt->isValidRedirectTarget() ) {
1024 // We somehow got a bad redirect target into the database (T278367)
1025 return false;
1026 }
1027
1028 return $rt;
1029 }
1030
1036 public function getContributors() {
1037 // @todo: This is expensive; cache this info somewhere.
1038
1039 $services = MediaWikiServices::getInstance();
1040 $dbr = $services->getConnectionProvider()->getReplicaDatabase();
1041 $actorNormalization = $services->getActorNormalization();
1042 $userIdentityLookup = $services->getUserIdentityLookup();
1043
1044 $user = $this->getUser()
1045 ? User::newFromId( $this->getUser() )
1046 : User::newFromName( $this->getUserText(), false );
1047
1048 $res = $dbr->newSelectQueryBuilder()
1049 ->select( [
1050 'user_id' => 'actor_user',
1051 'user_name' => 'actor_name',
1052 'actor_id' => 'MIN(rev_actor)',
1053 'user_real_name' => 'MIN(user_real_name)',
1054 'timestamp' => 'MAX(rev_timestamp)',
1055 ] )
1056 ->from( 'revision' )
1057 ->join( 'actor', null, 'rev_actor = actor_id' )
1058 ->leftJoin( 'user', null, 'actor_user = user_id' )
1059 ->where( [
1060 'rev_page' => $this->getId(),
1061 // The user who made the top revision gets credited as "this page was last edited by
1062 // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
1063 $dbr->expr( 'rev_actor', '!=', $actorNormalization->findActorId( $user, $dbr ) ),
1064 // Username hidden?
1065 $dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0',
1066 ] )
1067 ->groupBy( [ 'actor_user', 'actor_name' ] )
1068 ->orderBy( 'timestamp', SelectQueryBuilder::SORT_DESC )
1069 ->caller( __METHOD__ )
1070 ->fetchResultSet();
1071 return new UserArrayFromResult( $res );
1072 }
1073
1081 public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
1082 // NOTE: Keep in sync with ParserOutputAccess::shouldUseCache().
1083 // TODO: Once ParserOutputAccess is stable, deprecated this method.
1084 return $this->exists()
1085 && ( $oldId === null || $oldId === 0 || $oldId === $this->getLatest() )
1086 && $this->getContentHandler()->isParserCacheSupported();
1087 }
1088
1104 public function getParserOutput(
1105 ?ParserOptions $parserOptions = null, $oldid = null, $noCache = false
1106 ) {
1107 if ( $oldid ) {
1108 $revision = $this->getRevisionStore()->getRevisionByTitle( $this->getTitle(), $oldid );
1109
1110 if ( !$revision ) {
1111 return false;
1112 }
1113 } else {
1114 $revision = $this->getRevisionRecord();
1115 }
1116
1117 if ( !$parserOptions ) {
1118 $parserOptions = ParserOptions::newFromAnon();
1119 }
1120
1121 $options = $noCache ? ParserOutputAccess::OPT_NO_CACHE : 0;
1122
1123 $status = MediaWikiServices::getInstance()->getParserOutputAccess()->getParserOutput(
1124 $this, $parserOptions, $revision, $options
1125 );
1126 return $status->isOK() ? $status->getValue() : false; // convert null to false
1127 }
1128
1135 public function doViewUpdates(
1136 Authority $performer,
1137 $oldid = 0,
1138 ?RevisionRecord $oldRev = null
1139 ) {
1140 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
1141 return;
1142 }
1143
1144 DeferredUpdates::addCallableUpdate(
1145 function () use ( $performer ) {
1146 // In practice, these hook handlers simply debounce into a post-send
1147 // to do their work since none of the use cases for this hook require
1148 // a blocking pre-send callback.
1149 //
1150 // TODO: Move this hook to post-send.
1151 //
1152 // For now, it is unofficially possible for an extension to use
1153 // onPageViewUpdates to try to insert JavaScript via global $wgOut.
1154 // This isn't supported (the hook doesn't pass OutputPage), and
1155 // can't be since OutputPage may be disabled or replaced on some
1156 // pages that we do support page view updates for. We also run
1157 // this hook after HTMLFileCache, which also naturally can't
1158 // support modifying OutputPage. Handlers that modify the page
1159 // may use onBeforePageDisplay instead, which runs behind
1160 // HTMLFileCache and won't run on non-OutputPage responses.
1161 $legacyUser = MediaWikiServices::getInstance()
1162 ->getUserFactory()
1163 ->newFromAuthority( $performer );
1164 $this->getHookRunner()->onPageViewUpdates( $this, $legacyUser );
1165 },
1166 DeferredUpdates::PRESEND
1167 );
1168
1169 // Update newtalk and watchlist notification status
1170 MediaWikiServices::getInstance()
1171 ->getWatchlistManager()
1172 ->clearTitleUserNotifications( $performer, $this, $oldid, $oldRev );
1173 }
1174
1181 public function doPurge() {
1182 if ( !$this->getHookRunner()->onArticlePurge( $this ) ) {
1183 return false;
1184 }
1185
1186 $this->mTitle->invalidateCache();
1187
1188 // Clear file cache and send purge after above page_touched update was committed
1189 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
1190 $hcu->purgeTitleUrls( $this->mTitle, $hcu::PURGE_PRESEND );
1191
1192 if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI ) {
1193 MediaWikiServices::getInstance()->getMessageCache()
1194 ->updateMessageOverride( $this->mTitle, $this->getContent() );
1195 }
1196 InfoAction::invalidateCache( $this->mTitle, $this->getLatest() );
1197
1198 return true;
1199 }
1200
1219 public function insertOn( $dbw, $pageId = null ) {
1220 $pageIdForInsert = $pageId ? [ 'page_id' => $pageId ] : [];
1221 $dbw->newInsertQueryBuilder()
1222 ->insertInto( 'page' )
1223 ->ignore()
1224 ->row( [
1225 'page_namespace' => $this->mTitle->getNamespace(),
1226 'page_title' => $this->mTitle->getDBkey(),
1227 'page_is_redirect' => 0, // Will set this shortly...
1228 'page_is_new' => 1,
1229 'page_random' => wfRandom(),
1230 'page_touched' => $dbw->timestamp(),
1231 'page_latest' => 0, // Fill this in shortly...
1232 'page_len' => 0, // Fill this in shortly...
1233 ] + $pageIdForInsert )
1234 ->caller( __METHOD__ )->execute();
1235
1236 if ( $dbw->affectedRows() > 0 ) {
1237 $newid = $pageId ? (int)$pageId : $dbw->insertId();
1238 $this->mId = $newid;
1239 $this->mTitle->resetArticleID( $newid );
1240
1241 return $newid;
1242 } else {
1243 return false; // nothing changed
1244 }
1245 }
1246
1264 public function updateRevisionOn(
1265 $dbw,
1266 RevisionRecord $revision,
1267 $lastRevision = null,
1268 $lastRevIsRedirect = null
1269 ) {
1270 // TODO: move into PageUpdater or PageStore
1271 // NOTE: when doing that, make sure cached fields get reset in doUserEditContent,
1272 // and in the compat stub!
1273
1274 $revId = $revision->getId();
1275 Assert::parameter( $revId > 0, '$revision->getId()', 'must be > 0' );
1276
1277 $content = $revision->getContent( SlotRecord::MAIN );
1278 $len = $content ? $content->getSize() : 0;
1279 $rt = $content ? $content->getRedirectTarget() : null;
1280 $isNew = $lastRevision === 0;
1281 $isRedirect = $rt !== null;
1282
1283 $conditions = [ 'page_id' => $this->getId() ];
1284
1285 if ( $lastRevision !== null ) {
1286 // An extra check against threads stepping on each other
1287 $conditions['page_latest'] = $lastRevision;
1288 }
1289
1290 $model = $revision->getMainContentModel();
1291
1292 $row = [ /* SET */
1293 'page_latest' => $revId,
1294 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
1295 'page_is_new' => $isNew ? 1 : 0,
1296 'page_is_redirect' => $isRedirect ? 1 : 0,
1297 'page_len' => $len,
1298 'page_content_model' => $model,
1299 ];
1300
1301 $dbw->newUpdateQueryBuilder()
1302 ->update( 'page' )
1303 ->set( $row )
1304 ->where( $conditions )
1305 ->caller( __METHOD__ )->execute();
1306
1307 $result = $dbw->affectedRows() > 0;
1308 if ( $result ) {
1309 $insertedRow = $this->pageData( $dbw, [ 'page_id' => $this->getId() ] );
1310
1311 if ( !$insertedRow ) {
1312 throw new RuntimeException( 'Failed to load freshly inserted row' );
1313 }
1314
1315 $this->mTitle->loadFromRow( $insertedRow );
1316 MediaWikiServices::getInstance()->getRedirectStore()
1317 ->updateRedirectTarget( $this, $rt, $lastRevIsRedirect );
1318 $this->setLastEdit( $revision );
1319 $this->mPageIsRedirectField = (bool)$rt;
1320 $this->mIsNew = $isNew;
1321
1322 // Update the LinkCache.
1323 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1324 $linkCache->addGoodLinkObjFromRow(
1325 $this->mTitle,
1326 $insertedRow
1327 );
1328 }
1329
1330 return $result;
1331 }
1332
1346 $aSlots = $a->getSlots();
1347 $bSlots = $b->getSlots();
1348 $changedRoles = $aSlots->getRolesWithDifferentContent( $bSlots );
1349
1350 return ( $changedRoles !== [ SlotRecord::MAIN ] && $changedRoles !== [] );
1351 }
1352
1363 public function supportsSections() {
1364 return $this->getContentHandler()->supportsSections();
1365 }
1366
1380 public function replaceSectionContent(
1381 $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
1382 ) {
1383 $baseRevId = null;
1384 if ( $edittime && $sectionId !== 'new' ) {
1385 $lb = $this->getDBLoadBalancer();
1386 $rev = $this->getRevisionStore()->getRevisionByTimestamp( $this->mTitle, $edittime );
1387 // Try the primary database if this thread may have just added it.
1388 // The logic to fallback to the primary database if the replica is missing
1389 // the revision could be generalized into RevisionStore, but we don't want
1390 // to encourage loading of revisions by timestamp.
1391 if ( !$rev
1392 && $lb->hasReplicaServers()
1393 && $lb->hasOrMadeRecentPrimaryChanges()
1394 ) {
1395 $rev = $this->getRevisionStore()->getRevisionByTimestamp(
1396 $this->mTitle, $edittime, IDBAccessObject::READ_LATEST );
1397 }
1398 if ( $rev ) {
1399 $baseRevId = $rev->getId();
1400 }
1401 }
1402
1403 return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
1404 }
1405
1418 public function replaceSectionAtRev( $sectionId, Content $sectionContent,
1419 $sectionTitle = '', $baseRevId = null
1420 ) {
1421 if ( strval( $sectionId ) === '' ) {
1422 // Whole-page edit; let the whole text through
1423 $newContent = $sectionContent;
1424 } else {
1425 if ( !$this->supportsSections() ) {
1426 throw new BadMethodCallException( "sections not supported for content model " .
1427 $this->getContentHandler()->getModelID() );
1428 }
1429
1430 // T32711: always use current version when adding a new section
1431 if ( $baseRevId === null || $sectionId === 'new' ) {
1432 $oldContent = $this->getContent();
1433 } else {
1434 $revRecord = $this->getRevisionStore()->getRevisionById( $baseRevId );
1435 if ( !$revRecord ) {
1436 wfDebug( __METHOD__ . " asked for bogus section (page: " .
1437 $this->getId() . "; section: $sectionId)" );
1438 return null;
1439 }
1440
1441 $oldContent = $revRecord->getContent( SlotRecord::MAIN );
1442 }
1443
1444 if ( !$oldContent ) {
1445 wfDebug( __METHOD__ . ": no page text" );
1446 return null;
1447 }
1448
1449 $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
1450 }
1451
1452 return $newContent;
1453 }
1454
1464 public function checkFlags( $flags ) {
1465 if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
1466 if ( $this->exists() ) {
1467 $flags |= EDIT_UPDATE;
1468 } else {
1469 $flags |= EDIT_NEW;
1470 }
1471 }
1472
1473 return $flags;
1474 }
1475
1503 private function getDerivedDataUpdater(
1504 ?UserIdentity $forUser = null,
1505 ?RevisionRecord $forRevision = null,
1506 ?RevisionSlotsUpdate $forUpdate = null,
1507 $forEdit = false
1508 ) {
1509 if ( !$forRevision && !$forUpdate ) {
1510 // NOTE: can't re-use an existing derivedDataUpdater if we don't know what the caller is
1511 // going to use it with.
1512 $this->derivedDataUpdater = null;
1513 }
1514
1515 if ( $this->derivedDataUpdater && !$this->derivedDataUpdater->isContentPrepared() ) {
1516 // NOTE: can't re-use an existing derivedDataUpdater if other code that has a reference
1517 // to it did not yet initialize it, because we don't know what data it will be
1518 // initialized with.
1519 $this->derivedDataUpdater = null;
1520 }
1521
1522 // XXX: It would be nice to have an LRU cache instead of trying to re-use a single instance.
1523 // However, there is no good way to construct a cache key. We'd need to check against all
1524 // cached instances.
1525
1526 if ( $this->derivedDataUpdater
1527 && !$this->derivedDataUpdater->isReusableFor(
1528 $forUser,
1529 $forRevision,
1530 $forUpdate,
1531 $forEdit ? $this->getLatest() : null
1532 )
1533 ) {
1534 $this->derivedDataUpdater = null;
1535 }
1536
1537 if ( !$this->derivedDataUpdater ) {
1538 $this->derivedDataUpdater =
1539 $this->getPageUpdaterFactory()->newDerivedPageDataUpdater( $this );
1540 }
1541
1542 return $this->derivedDataUpdater;
1543 }
1544
1592 public function doUserEditContent(
1593 Content $content,
1594 Authority $performer,
1595 $summary,
1596 $flags = 0,
1597 $originalRevId = false,
1598 $tags = [],
1599 $undidRevId = 0
1600 ): PageUpdateStatus {
1601 $useNPPatrol = MediaWikiServices::getInstance()->getMainConfig()->get(
1602 MainConfigNames::UseNPPatrol );
1603 $useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get(
1604 MainConfigNames::UseRCPatrol );
1605 if ( !( $summary instanceof CommentStoreComment ) ) {
1606 $summary = CommentStoreComment::newUnsavedComment( trim( $summary ) );
1607 }
1608
1609 // TODO: this check is here for backwards-compatibility with 1.31 behavior.
1610 // Checking the minoredit right should be done in the same place the 'bot' right is
1611 // checked for the EDIT_FORCE_BOT flag, which is currently in EditPage::attemptSave.
1612 if ( ( $flags & EDIT_MINOR ) && !$performer->isAllowed( 'minoredit' ) ) {
1613 $flags &= ~EDIT_MINOR;
1614 }
1615
1616 $slotsUpdate = new RevisionSlotsUpdate();
1617 $slotsUpdate->modifyContent( SlotRecord::MAIN, $content );
1618
1619 // NOTE: while doUserEditContent() executes, callbacks to getDerivedDataUpdater and
1620 // prepareContentForEdit will generally use the DerivedPageDataUpdater that is also
1621 // used by this PageUpdater. However, there is no guarantee for this.
1622 $updater = $this->newPageUpdater( $performer, $slotsUpdate )
1623 ->setContent( SlotRecord::MAIN, $content )
1624 ->setOriginalRevisionId( $originalRevId );
1625 if ( $undidRevId ) {
1626 $updater->setCause( PageUpdateCauses::CAUSE_UNDO );
1627 $updater->markAsRevert(
1628 EditResult::REVERT_UNDO,
1629 $undidRevId,
1630 $originalRevId ?: null
1631 );
1632 }
1633
1634 $needsPatrol = $useRCPatrol || ( $useNPPatrol && !$this->exists() );
1635
1636 // TODO: this logic should not be in the storage layer, it's here for compatibility
1637 // with 1.31 behavior. Applying the 'autopatrol' right should be done in the same
1638 // place the 'bot' right is handled, which is currently in EditPage::attemptSave.
1639
1640 if ( $needsPatrol && $performer->authorizeWrite( 'autopatrol', $this->getTitle() ) ) {
1641 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
1642 }
1643
1644 $updater->addTags( $tags );
1645
1646 $revRec = $updater->saveRevision(
1647 $summary,
1648 $flags
1649 );
1650
1651 // $revRec will be null if the edit failed, or if no new revision was created because
1652 // the content did not change.
1653 if ( $revRec ) {
1654 // update cached fields
1655 // TODO: this is currently redundant to what is done in updateRevisionOn.
1656 // But updateRevisionOn() should move into PageStore, and then this will be needed.
1657 $this->setLastEdit( $revRec );
1658 }
1659
1660 return $updater->getStatus();
1661 }
1662
1683 public function newPageUpdater( $performer, ?RevisionSlotsUpdate $forUpdate = null ) {
1684 if ( $performer instanceof Authority ) {
1685 // TODO: Deprecate this. But better get rid of this method entirely.
1686 $performer = $performer->getUser();
1687 }
1688
1689 $pageUpdater = $this->getPageUpdaterFactory()->newPageUpdaterForDerivedPageDataUpdater(
1690 $this,
1691 $performer,
1692 $this->getDerivedDataUpdater( $performer, null, $forUpdate, true )
1693 );
1694
1695 return $pageUpdater;
1696 }
1697
1712 public function makeParserOptions( $context ) {
1713 return self::makeParserOptionsFromTitleAndModel(
1714 $this->getTitle(), $this->getContentModel(), $context
1715 );
1716 }
1717
1727 PageReference $pageRef, string $contentModel, $context
1728 ) {
1729 $options = ParserOptions::newCanonical( $context );
1730
1731 $title = Title::newFromPageReference( $pageRef );
1732 if ( $title->isConversionTable() ) {
1733 // @todo ConversionTable should become a separate content model, so
1734 // we don't need special cases like this one, but see T313455.
1735 $options->disableContentConversion();
1736 }
1737
1738 return $options;
1739 }
1740
1761 public function prepareContentForEdit(
1762 Content $content,
1763 ?RevisionRecord $revision,
1764 UserIdentity $user,
1765 $serialFormat = null,
1766 $useStash = true
1767 ) {
1768 $slots = RevisionSlotsUpdate::newFromContent( [ SlotRecord::MAIN => $content ] );
1769 $updater = $this->getDerivedDataUpdater( $user, $revision, $slots );
1770
1771 if ( !$updater->isUpdatePrepared() ) {
1772 $updater->prepareContent( $user, $slots, $useStash );
1773
1774 if ( $revision ) {
1775 $updater->prepareUpdate(
1776 $revision,
1777 [
1778 'causeAction' => 'prepare-edit',
1779 'causeAgent' => $user->getName(),
1780 ]
1781 );
1782 }
1783 }
1784
1785 return $updater->getPreparedEdit();
1786 }
1787
1803 public function doEditUpdates(
1804 RevisionRecord $revisionRecord,
1805 UserIdentity $user,
1806 array $options = []
1807 ) {
1808 wfDeprecated( __METHOD__, '1.32' ); // emitting warnings since 1.44
1809
1810 $options += [
1811 'causeAction' => 'edit-page',
1812 'causeAgent' => $user->getName(),
1813 'emitEvents' => false // prior page state is unknown, can't emit events
1814 ];
1815
1816 $updater = $this->getDerivedDataUpdater( $user, $revisionRecord );
1817
1818 $updater->prepareUpdate( $revisionRecord, $options );
1819
1820 $updater->doUpdates();
1821 }
1822
1835 public function updateParserCache( array $options = [] ) {
1836 $revision = $this->getRevisionRecord();
1837 if ( !$revision || !$revision->getId() ) {
1838 LoggerFactory::getInstance( 'wikipage' )->info(
1839 __METHOD__ . ' called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
1840 );
1841 return;
1842 }
1843 $userIdentity = $revision->getUser( RevisionRecord::RAW );
1844
1845 $updater = $this->getDerivedDataUpdater( $userIdentity, $revision );
1846 $updater->prepareUpdate( $revision, $options );
1847 $updater->doParserCacheUpdate();
1848 }
1849
1878 public function doSecondaryDataUpdates( array $options = [] ) {
1879 $options['recursive'] ??= true;
1880 $revision = $this->getRevisionRecord();
1881 if ( !$revision || !$revision->getId() ) {
1882 LoggerFactory::getInstance( 'wikipage' )->info(
1883 __METHOD__ . ' called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
1884 );
1885 return;
1886 }
1887 $userIdentity = $revision->getUser( RevisionRecord::RAW );
1888
1889 $updater = $this->getDerivedDataUpdater( $userIdentity, $revision );
1890 $updater->prepareUpdate( $revision, $options );
1891 $updater->doSecondaryDataUpdates( $options );
1892 }
1893
1908 public function doUpdateRestrictions( array $limit, array $expiry,
1909 &$cascade, $reason, UserIdentity $user, $tags = []
1910 ) {
1911 $services = MediaWikiServices::getInstance();
1912 $readOnlyMode = $services->getReadOnlyMode();
1913 if ( $readOnlyMode->isReadOnly() ) {
1914 return Status::newFatal( wfMessage( 'readonlytext', $readOnlyMode->getReason() ) );
1915 }
1916
1917 $this->loadPageData( 'fromdbmaster' );
1918 $restrictionStore = $services->getRestrictionStore();
1919 $restrictionStore->loadRestrictions( $this->mTitle, IDBAccessObject::READ_LATEST );
1920 $restrictionTypes = $restrictionStore->listApplicableRestrictionTypes( $this->mTitle );
1921 $id = $this->getId();
1922
1923 if ( !$cascade ) {
1924 $cascade = false;
1925 }
1926
1927 // Take this opportunity to purge out expired restrictions
1928 Title::purgeExpiredRestrictions();
1929
1930 // @todo: Same limitations as described in ProtectionForm.php (line 37);
1931 // we expect a single selection, but the schema allows otherwise.
1932 $isProtected = false;
1933 $protect = false;
1934 $changed = false;
1935
1936 $dbw = $services->getConnectionProvider()->getPrimaryDatabase();
1937 $restrictionMapBefore = [];
1938 $restrictionMapAfter = [];
1939
1940 foreach ( $restrictionTypes as $action ) {
1941 if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) {
1942 $expiry[$action] = 'infinity';
1943 }
1944
1945 // Get current restrictions on $action
1946 $restrictionMapBefore[$action] = $restrictionStore->getRestrictions( $this->mTitle, $action );
1947 $limit[$action] ??= '';
1948
1949 if ( $limit[$action] === '' ) {
1950 $restrictionMapAfter[$action] = [];
1951 } else {
1952 $protect = true;
1953 $restrictionMapAfter[$action] = explode( ',', $limit[$action] );
1954 }
1955
1956 $current = implode( ',', $restrictionMapBefore[$action] );
1957 if ( $current != '' ) {
1958 $isProtected = true;
1959 }
1960
1961 if ( $limit[$action] != $current ) {
1962 $changed = true;
1963 } elseif ( $limit[$action] != '' ) {
1964 // Only check expiry change if the action is actually being
1965 // protected, since expiry does nothing on an not-protected
1966 // action.
1967 if ( $restrictionStore->getRestrictionExpiry( $this->mTitle, $action ) != $expiry[$action] ) {
1968 $changed = true;
1969 }
1970 }
1971 }
1972
1973 if ( !$changed && $protect && $restrictionStore->areRestrictionsCascading( $this->mTitle ) != $cascade ) {
1974 $changed = true;
1975 }
1976
1977 // If nothing has changed, do nothing
1978 if ( !$changed ) {
1979 return Status::newGood();
1980 }
1981
1982 if ( !$protect ) { // No protection at all means unprotection
1983 $revCommentMsg = 'unprotectedarticle-comment';
1984 $logAction = 'unprotect';
1985 } elseif ( $isProtected ) {
1986 $revCommentMsg = 'modifiedarticleprotection-comment';
1987 $logAction = 'modify';
1988 } else {
1989 $revCommentMsg = 'protectedarticle-comment';
1990 $logAction = 'protect';
1991 }
1992
1993 $logRelationsValues = [];
1994 $logRelationsField = null;
1995 $logParamsDetails = [];
1996
1997 // Null revision (used for change tag insertion)
1998 $dummyRevisionRecord = null;
1999
2000 $legacyUser = $services->getUserFactory()->newFromUserIdentity( $user );
2001 if ( !$this->getHookRunner()->onArticleProtect( $this, $legacyUser, $limit, $reason ) ) {
2002 return Status::newGood();
2003 }
2004
2005 if ( $id ) { // Protection of existing page
2006 // Only certain restrictions can cascade...
2007 $editrestriction = isset( $limit['edit'] )
2008 ? [ $limit['edit'] ]
2009 : $restrictionStore->getRestrictions( $this->mTitle, 'edit' );
2010 foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
2011 $editrestriction[$key] = 'editprotected'; // backwards compatibility
2012 }
2013 foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
2014 $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
2015 }
2016
2017 $cascadingRestrictionLevels = $services->getMainConfig()
2018 ->get( MainConfigNames::CascadingRestrictionLevels );
2019
2020 foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
2021 $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
2022 }
2023 foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
2024 $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
2025 }
2026
2027 // The schema allows multiple restrictions
2028 if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
2029 $cascade = false;
2030 }
2031
2032 // insert dummy revision to identify the page protection change as edit summary
2033 $dummyRevisionRecord = $this->insertNullProtectionRevision(
2034 $revCommentMsg,
2035 $limit,
2036 $expiry,
2037 $cascade,
2038 $reason,
2039 $user
2040 );
2041
2042 if ( $dummyRevisionRecord === null ) {
2043 return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
2044 }
2045
2046 $logRelationsField = 'pr_id';
2047
2048 // T214035: Avoid deadlock on MySQL.
2049 // Do a DELETE by primary key (pr_id) for any existing protection rows.
2050 // On MySQL and derivatives, unconditionally deleting by page ID (pr_page) would.
2051 // place a gap lock if there are no matching rows. This can deadlock when another
2052 // thread modifies protection settings for page IDs in the same gap.
2053 $existingProtectionIds = $dbw->newSelectQueryBuilder()
2054 ->select( 'pr_id' )
2055 ->from( 'page_restrictions' )
2056 ->where( [ 'pr_page' => $id, 'pr_type' => array_map( 'strval', array_keys( $limit ) ) ] )
2057 ->caller( __METHOD__ )->fetchFieldValues();
2058
2059 if ( $existingProtectionIds ) {
2060 $dbw->newDeleteQueryBuilder()
2061 ->deleteFrom( 'page_restrictions' )
2062 ->where( [ 'pr_id' => $existingProtectionIds ] )
2063 ->caller( __METHOD__ )->execute();
2064 }
2065
2066 // Update restrictions table
2067 foreach ( $limit as $action => $restrictions ) {
2068 if ( $restrictions != '' ) {
2069 $cascadeValue = ( $cascade && $action == 'edit' ) ? 1 : 0;
2070 $dbw->newInsertQueryBuilder()
2071 ->insertInto( 'page_restrictions' )
2072 ->row( [
2073 'pr_page' => $id,
2074 'pr_type' => $action,
2075 'pr_level' => $restrictions,
2076 'pr_cascade' => $cascadeValue,
2077 'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
2078 ] )
2079 ->caller( __METHOD__ )->execute();
2080 $logRelationsValues[] = $dbw->insertId();
2081 $logParamsDetails[] = [
2082 'type' => $action,
2083 'level' => $restrictions,
2084 'expiry' => $expiry[$action],
2085 'cascade' => (bool)$cascadeValue,
2086 ];
2087 }
2088 }
2089 } else { // Protection of non-existing page (also known as "title protection")
2090 // Cascade protection is meaningless in this case
2091 $cascade = false;
2092
2093 if ( $limit['create'] != '' ) {
2094 $commentFields = $services->getCommentStore()->insert( $dbw, 'pt_reason', $reason );
2095 $dbw->newReplaceQueryBuilder()
2096 ->table( 'protected_titles' )
2097 ->uniqueIndexFields( [ 'pt_namespace', 'pt_title' ] )
2098 ->rows( [
2099 'pt_namespace' => $this->mTitle->getNamespace(),
2100 'pt_title' => $this->mTitle->getDBkey(),
2101 'pt_create_perm' => $limit['create'],
2102 'pt_timestamp' => $dbw->timestamp(),
2103 'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
2104 'pt_user' => $user->getId(),
2105 ] + $commentFields )
2106 ->caller( __METHOD__ )->execute();
2107 $logParamsDetails[] = [
2108 'type' => 'create',
2109 'level' => $limit['create'],
2110 'expiry' => $expiry['create'],
2111 ];
2112 } else {
2113 $dbw->newDeleteQueryBuilder()
2114 ->deleteFrom( 'protected_titles' )
2115 ->where( [
2116 'pt_namespace' => $this->mTitle->getNamespace(),
2117 'pt_title' => $this->mTitle->getDBkey()
2118 ] )
2119 ->caller( __METHOD__ )->execute();
2120 }
2121 }
2122
2123 $this->getHookRunner()->onArticleProtectComplete( $this, $legacyUser, $limit, $reason );
2124
2125 $restrictionStore->flushRestrictions( $this->mTitle );
2126
2127 InfoAction::invalidateCache( $this->mTitle );
2128
2129 if ( $logAction == 'unprotect' ) {
2130 $params = [];
2131 } else {
2132 $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
2133 $params = [
2134 '4::description' => $protectDescriptionLog, // parameter for IRC
2135 '5:bool:cascade' => $cascade,
2136 'details' => $logParamsDetails, // parameter for localize and api
2137 ];
2138 }
2139
2140 // Update the protection log
2141 $logEntry = new ManualLogEntry( 'protect', $logAction );
2142 $logEntry->setTarget( $this->mTitle );
2143 $logEntry->setComment( $reason );
2144 $logEntry->setPerformer( $user );
2145 $logEntry->setParameters( $params );
2146 if ( $dummyRevisionRecord !== null ) {
2147 $logEntry->setAssociatedRevId( $dummyRevisionRecord->getId() );
2148 }
2149 $logEntry->addTags( $tags );
2150 if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
2151 $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
2152 }
2153 $logId = $logEntry->insert();
2154 $logEntry->publish( $logId );
2155
2156 $event = new PageProtectionChangedEvent(
2157 $this,
2158 $restrictionMapBefore,
2159 $restrictionMapAfter,
2160 $expiry,
2161 $cascade,
2162 $user,
2163 $reason,
2164 $tags
2165 );
2166
2167 $dispatcher = MediaWikiServices::getInstance()->getDomainEventDispatcher();
2168 $dispatcher->dispatch( $event, $services->getConnectionProvider() );
2169
2170 return Status::newGood( $logId );
2171 }
2172
2194 Assert::precondition(
2195 $this->derivedDataUpdater !== null,
2196 'There is no ongoing update tracked by this instance of WikiPage!'
2197 );
2198
2199 return $this->derivedDataUpdater;
2200 }
2201
2217 string $revCommentMsg,
2218 array $limit,
2219 array $expiry,
2220 bool $cascade,
2221 string $reason,
2222 UserIdentity $user
2223 ): ?RevisionRecord {
2224 // Prepare a dummy revision to be added to the history
2225 $editComment = wfMessage(
2226 $revCommentMsg,
2227 $this->mTitle->getPrefixedText(),
2228 $user->getName()
2229 )->inContentLanguage()->text();
2230 if ( $reason ) {
2231 $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
2232 }
2233 $protectDescription = $this->protectDescription( $limit, $expiry );
2234 if ( $protectDescription ) {
2235 $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2236 $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )
2237 ->inContentLanguage()->text();
2238 }
2239 if ( $cascade ) {
2240 $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2241 $editComment .= wfMessage( 'brackets' )->params(
2242 wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
2243 )->inContentLanguage()->text();
2244 }
2245
2246 return $this->newPageUpdater( $user )
2247 ->setCause( PageUpdater::CAUSE_PROTECTION_CHANGE )
2248 ->saveDummyRevision( $editComment, EDIT_SILENT | EDIT_MINOR );
2249 }
2250
2255 protected function formatExpiry( $expiry ) {
2256 if ( $expiry != 'infinity' ) {
2257 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2258 return wfMessage(
2259 'protect-expiring',
2260 $contLang->timeanddate( $expiry, false, false ),
2261 $contLang->date( $expiry, false, false ),
2262 $contLang->time( $expiry, false, false )
2263 )->inContentLanguage()->text();
2264 } else {
2265 return wfMessage( 'protect-expiry-indefinite' )
2266 ->inContentLanguage()->text();
2267 }
2268 }
2269
2277 public function protectDescription( array $limit, array $expiry ) {
2278 $protectDescription = '';
2279
2280 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2281 # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
2282 # All possible message keys are listed here for easier grepping:
2283 # * restriction-create
2284 # * restriction-edit
2285 # * restriction-move
2286 # * restriction-upload
2287 $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
2288 # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
2289 # with '' filtered out. All possible message keys are listed below:
2290 # * protect-level-autoconfirmed
2291 # * protect-level-sysop
2292 $restrictionsText = wfMessage( 'protect-level-' . $restrictions )
2293 ->inContentLanguage()->text();
2294
2295 $expiryText = $this->formatExpiry( $expiry[$action] );
2296
2297 if ( $protectDescription !== '' ) {
2298 $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2299 }
2300 $protectDescription .= wfMessage( 'protect-summary-desc' )
2301 ->params( $actionText, $restrictionsText, $expiryText )
2302 ->inContentLanguage()->text();
2303 }
2304
2305 return $protectDescription;
2306 }
2307
2319 public function protectDescriptionLog( array $limit, array $expiry ) {
2320 $protectDescriptionLog = '';
2321
2322 $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark();
2323 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2324 $expiryText = $this->formatExpiry( $expiry[$action] );
2325 $protectDescriptionLog .=
2326 $dirMark .
2327 "[$action=$restrictions] ($expiryText)";
2328 }
2329
2330 return trim( $protectDescriptionLog );
2331 }
2332
2347 public function isBatchedDelete( $safetyMargin = 0 ) {
2348 $deleteRevisionsBatchSize = MediaWikiServices::getInstance()
2349 ->getMainConfig()->get( MainConfigNames::DeleteRevisionsBatchSize );
2350
2351 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
2352 $revCount = $this->getRevisionStore()->countRevisionsByPageId( $dbr, $this->getId() );
2353 $revCount += $safetyMargin;
2354
2355 return $revCount >= $deleteRevisionsBatchSize;
2356 }
2357
2386 public function doDeleteArticleReal(
2387 $reason, UserIdentity $deleter, $suppress = false, $u1 = null, &$error = '', $u2 = null,
2388 $tags = [], $logsubtype = 'delete', $immediate = false
2389 ) {
2390 $services = MediaWikiServices::getInstance();
2391 $deletePage = $services->getDeletePageFactory()->newDeletePage(
2392 $this,
2393 $services->getUserFactory()->newFromUserIdentity( $deleter )
2394 );
2395
2396 $status = $deletePage
2397 ->setSuppress( $suppress )
2398 ->setTags( $tags ?: [] )
2399 ->setLogSubtype( $logsubtype )
2400 ->forceImmediate( $immediate )
2401 ->keepLegacyHookErrorsSeparate()
2402 ->deleteUnsafe( $reason );
2403 $error = $deletePage->getLegacyHookErrors();
2404 if ( $status->isGood() ) {
2405 // BC with old return format
2406 if ( $deletePage->deletionsWereScheduled()[DeletePage::PAGE_BASE] ) {
2407 $status->warning( 'delete-scheduled', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
2408 } else {
2409 // @phan-suppress-next-line PhanTypeMismatchProperty Changing the type of the status parameter
2410 $status->value = $deletePage->getSuccessfulDeletionsIDs()[DeletePage::PAGE_BASE];
2411 }
2412 }
2413 return $status;
2414 }
2415
2422 public function lockAndGetLatest() {
2423 $dbw = $this->getConnectionProvider()->getPrimaryDatabase();
2424 return (int)$dbw->newSelectQueryBuilder()
2425 ->select( 'page_latest' )
2426 ->forUpdate()
2427 ->from( 'page' )
2428 ->where( [
2429 'page_id' => $this->getId(),
2430 // Typically page_id is enough, but some code might try to do
2431 // updates assuming the title is the same, so verify that
2432 'page_namespace' => $this->getTitle()->getNamespace(),
2433 'page_title' => $this->getTitle()->getDBkey()
2434 ] )
2435 ->caller( __METHOD__ )->fetchField();
2436 }
2437
2451 public static function onArticleCreate( Title $title, $maybeIsRedirect = true ) {
2452 // TODO: move this into a PageEventEmitter service
2453
2454 // Update existence markers on article/talk tabs...
2455 $other = $title->getOtherPage();
2456
2457 $services = MediaWikiServices::getInstance();
2458 $hcu = $services->getHtmlCacheUpdater();
2459 $hcu->purgeTitleUrls( [ $title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2460
2461 $title->touchLinks();
2462 $services->getRestrictionStore()->deleteCreateProtection( $title );
2463
2464 $services->getLinkCache()->invalidateTitle( $title );
2465
2466 DeferredUpdates::addCallableUpdate(
2467 static function () use ( $title, $maybeIsRedirect ) {
2468 self::queueBacklinksJobs( $title, true, $maybeIsRedirect, 'create-page' );
2469 }
2470 );
2471
2472 if ( $title->getNamespace() === NS_CATEGORY ) {
2473 // Load the Category object, which will schedule a job to create
2474 // the category table row if necessary. Checking a replica DB is ok
2475 // here, in the worst case it'll run an unnecessary recount job on
2476 // a category that probably doesn't have many members.
2477 Category::newFromTitle( $title )->getID();
2478 }
2479 }
2480
2489 public static function onArticleDelete( Title $title ) {
2490 // TODO: move this into a PageEventEmitter service
2491
2492 // Update existence markers on article/talk tabs...
2493 $other = $title->getOtherPage();
2494
2495 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2496 $hcu->purgeTitleUrls( [ $title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2497
2498 $title->touchLinks();
2499
2500 $services = MediaWikiServices::getInstance();
2501 $services->getLinkCache()->invalidateTitle( $title );
2502
2503 InfoAction::invalidateCache( $title );
2504
2505 // Invalidate caches of articles which include this page
2506 DeferredUpdates::addCallableUpdate( static function () use ( $title ) {
2507 self::queueBacklinksJobs( $title, true, true, 'delete-page' );
2508 } );
2509
2510 // TODO: Move to ChangeTrackingEventIngress when ready,
2511 // but make sure it happens on deletions and page moves by adding
2512 // the appropriate assertions to ChangeTrackingEventIngressSpyTrait.
2513 // Messages
2514 // User talk pages
2515 if ( $title->getNamespace() === NS_USER_TALK ) {
2516 $user = User::newFromName( $title->getText(), false );
2517 if ( $user ) {
2518 MediaWikiServices::getInstance()
2519 ->getTalkPageNotificationManager()
2520 ->removeUserHasNewMessages( $user );
2521 }
2522 }
2523
2524 // TODO: Create MediaEventIngress and move this there.
2525 // Image redirects
2526 $services->getRepoGroup()->getLocalRepo()->invalidateImageRedirect( $title );
2527
2528 // Purge cross-wiki cache entities referencing this page
2529 self::purgeInterwikiCheckKey( $title );
2530 }
2531
2542 public static function onArticleEdit(
2543 Title $title,
2544 ?RevisionRecord $revRecord = null,
2545 $slotsChanged = null,
2546 $maybeRedirectChanged = true
2547 ) {
2548 // TODO: move this into a PageEventEmitter service
2549
2550 DeferredUpdates::addCallableUpdate(
2551 static function () use ( $title, $slotsChanged, $maybeRedirectChanged ) {
2552 self::queueBacklinksJobs(
2553 $title,
2554 $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ),
2555 $maybeRedirectChanged,
2556 'edit-page'
2557 );
2558 }
2559 );
2560
2561 $services = MediaWikiServices::getInstance();
2562 $services->getLinkCache()->invalidateTitle( $title );
2563
2564 $hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
2565 $hcu->purgeTitleUrls( $title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2566
2567 // Purge ?action=info cache
2568 $revid = $revRecord ? $revRecord->getId() : null;
2569 DeferredUpdates::addCallableUpdate( static function () use ( $title, $revid ) {
2570 InfoAction::invalidateCache( $title, $revid );
2571 } );
2572
2573 // Purge cross-wiki cache entities referencing this page
2574 self::purgeInterwikiCheckKey( $title );
2575 }
2576
2577 private static function queueBacklinksJobs(
2578 Title $title, bool $mainSlotChanged, bool $maybeRedirectChanged, string $causeAction
2579 ) {
2580 $services = MediaWikiServices::getInstance();
2581 $backlinkCache = $services->getBacklinkCacheFactory()->getBacklinkCache( $title );
2582
2583 $jobs = [];
2584 if ( $mainSlotChanged
2585 && $backlinkCache->hasLinks( 'templatelinks' )
2586 ) {
2587 // Invalidate caches of articles which include this page.
2588 // Only for the main slot, because only the main slot is transcluded.
2589 // TODO: MCR: not true for TemplateStyles! [SlotHandler]
2590 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2591 $title,
2592 'templatelinks',
2593 [ 'causeAction' => $causeAction ]
2594 );
2595 }
2596 // Images
2597 if ( $maybeRedirectChanged && $title->getNamespace() === NS_FILE
2598 && $backlinkCache->hasLinks( 'imagelinks' )
2599 ) {
2600 // Process imagelinks in case the redirect target has changed
2601 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2602 $title,
2603 'imagelinks',
2604 [ 'causeAction' => $causeAction ]
2605 );
2606 }
2607 // Invalidate the caches of all pages which redirect here
2608 if ( $backlinkCache->hasLinks( 'redirect' ) ) {
2609 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2610 $title,
2611 'redirect',
2612 [ 'causeAction' => $causeAction ]
2613 );
2614 }
2615 if ( $jobs ) {
2616 $services->getJobQueueGroup()->push( $jobs );
2617 }
2618 }
2619
2623 private static function purgeInterwikiCheckKey( Title $title ) {
2624 $enableScaryTranscluding = MediaWikiServices::getInstance()->getMainConfig()->get(
2625 MainConfigNames::EnableScaryTranscluding );
2626
2627 if ( !$enableScaryTranscluding ) {
2628 return; // @todo: perhaps this wiki is only used as a *source* for content?
2629 }
2630
2631 DeferredUpdates::addCallableUpdate( static function () use ( $title ) {
2632 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2633 $cache->resetCheckKey(
2634 // Do not include the namespace since there can be multiple aliases to it
2635 // due to different namespace text definitions on different wikis. This only
2636 // means that some cache invalidations happen that are not strictly needed.
2637 $cache->makeGlobalKey(
2638 'interwiki-page',
2639 WikiMap::getCurrentWikiDbDomain()->getId(),
2640 $title->getDBkey()
2641 )
2642 );
2643 } );
2644 }
2645
2652 public function getCategories() {
2653 $services = MediaWikiServices::getInstance();
2654 $id = $this->getId();
2655 if ( $id == 0 ) {
2656 return $services->getTitleFactory()->newTitleArrayFromResult( new FakeResultWrapper( [] ) );
2657 }
2658
2659 $dbr = $services->getConnectionProvider()->getReplicaDatabase( CategoryLinksTable::VIRTUAL_DOMAIN );
2660 $res = $dbr->newSelectQueryBuilder()
2661 ->select( [ 'page_title' => 'lt_title', 'page_namespace' => (string)NS_CATEGORY ] )
2662 ->from( 'categorylinks' )
2663 ->join( 'linktarget', null, [ 'cl_target_id = lt_id', 'lt_namespace = ' . NS_CATEGORY ] )
2664 ->where( [ 'cl_from' => $id ] )
2665 ->caller( __METHOD__ )
2666 ->fetchResultSet();
2667
2668 return $services->getTitleFactory()->newTitleArrayFromResult( $res );
2669 }
2670
2677 public function getHiddenCategories() {
2678 $result = [];
2679 $id = $this->getId();
2680
2681 if ( $id == 0 ) {
2682 return [];
2683 }
2684
2685 $dbr = $this->getConnectionProvider()->getReplicaDatabase( CategoryLinksTable::VIRTUAL_DOMAIN );
2686 $res = $dbr->newSelectQueryBuilder()
2687 ->select( 'lt_title' )
2688 ->from( 'categorylinks' )
2689 ->join( 'linktarget', null, 'cl_target_id = lt_id' )
2690 ->join( 'page', null, [ 'page_title = lt_title', 'page_namespace = lt_namespace' ] )
2691 ->join( 'page_props', null, 'pp_page=page_id' )
2692 ->where( [ 'cl_from' => $id, 'pp_propname' => 'hiddencat', 'page_namespace' => NS_CATEGORY ] )
2693 ->caller( __METHOD__ )
2694 ->fetchResultSet();
2695
2696 foreach ( $res as $row ) {
2697 $result[] = Title::makeTitle( NS_CATEGORY, $row->lt_title );
2698 }
2699
2700 return $result;
2701 }
2702
2710 public function getAutoDeleteReason( &$hasHistory = false ) {
2711 if ( func_num_args() === 1 ) {
2712 wfDeprecated( __METHOD__ . ': $hasHistory parameter', '1.38' );
2713 return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
2714 }
2715 return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle() );
2716 }
2717
2730 public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2731 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
2732 return;
2733 }
2734
2735 if ( !$this->getHookRunner()->onOpportunisticLinksUpdate( $this,
2736 $this->mTitle, $parserOutput )
2737 ) {
2738 return;
2739 }
2740
2741 $config = MediaWikiServices::getInstance()->getMainConfig();
2742
2743 $params = [
2744 'isOpportunistic' => true,
2745 'rootJobTimestamp' => $parserOutput->getCacheTime()
2746 ];
2747
2748 if ( MediaWikiServices::getInstance()->getRestrictionStore()->areRestrictionsCascading( $this->mTitle ) ) {
2749 // In general, MediaWiki does not re-run LinkUpdate (e.g. for search index, category
2750 // listings, and backlinks for Whatlinkshere), unless either the page was directly
2751 // edited, or was re-generate following a template edit propagating to an affected
2752 // page. As such, during page views when there is no valid ParserCache entry,
2753 // we re-parse and save, but leave indexes as-is.
2754 //
2755 // We make an exception for pages that have cascading protection (perhaps for a wiki's
2756 // "Main Page"). When such page is re-parsed on-demand after a parser cache miss, we
2757 // queue a high-priority LinksUpdate job, to ensure that we really protect all
2758 // content that is currently transcluded onto the page. This is important, because
2759 // wikitext supports conditional statements based on the current time, which enables
2760 // transcluding of a different subpage based on which day it is, and then show that
2761 // information on the Main Page, without the Main Page itself being edited.
2762 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush(
2763 RefreshLinksJob::newPrioritized( $this->mTitle, $params )
2764 );
2765 } elseif (
2766 (
2767 // "Dynamic" content (eg time/random magic words)
2768 !$config->get( MainConfigNames::MiserMode ) &&
2769 $parserOutput->hasReducedExpiry()
2770 )
2771 ||
2772 (
2773 // Asynchronous content
2774 $config->get( MainConfigNames::ParserCacheAsyncRefreshJobs ) &&
2775 $parserOutput->getOutputFlag( ParserOutputFlags::HAS_ASYNC_CONTENT ) &&
2776 !$parserOutput->getOutputFlag( ParserOutputFlags::ASYNC_NOT_READY )
2777 )
2778 ) {
2779 // Assume the output contains "dynamic" time/random based magic words
2780 // or asynchronous content that wasn't "ready" the first time the
2781 // page was parsed.
2782 // Only update pages that expired due to dynamic content and NOT due to edits
2783 // to referenced templates/files. When the cache expires due to dynamic content,
2784 // page_touched is unchanged. We want to avoid triggering redundant jobs due to
2785 // views of pages that were just purged via HTMLCacheUpdateJob. In that case, the
2786 // template/file edit already triggered recursive RefreshLinksJob jobs.
2787 if ( $this->getLinksTimestamp() > $this->getTouched() ) {
2788 // If a page is uncacheable, do not keep spamming a job for it.
2789 // Although it would be de-duplicated, it would still waste I/O.
2790 $services = MediaWikiServices::getInstance()->getObjectCacheFactory();
2791 $cache = $services->getLocalClusterInstance();
2792 $key = $cache->makeKey( 'dynamic-linksupdate', 'last', $this->getId() );
2793 $ttl = max( $parserOutput->getCacheExpiry(), 3600 );
2794 if ( $cache->add( $key, time(), $ttl ) ) {
2795 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush(
2796 RefreshLinksJob::newDynamic( $this->mTitle, $params )
2797 );
2798 }
2799 }
2800 }
2801 }
2802
2810 public function isLocal() {
2811 return true;
2812 }
2813
2823 public function getWikiDisplayName() {
2824 $sitename = MediaWikiServices::getInstance()->getMainConfig()->get(
2825 MainConfigNames::Sitename );
2826 return $sitename;
2827 }
2828
2837 public function getSourceURL() {
2838 return $this->getTitle()->getCanonicalURL();
2839 }
2840
2847 public function __wakeup() {
2848 // Make sure we re-fetch the latest state from the database.
2849 // In particular, the latest revision may have changed.
2850 // As a side-effect, this makes sure mLastRevision doesn't
2851 // end up being an instance of the old Revision class (see T259181),
2852 // especially since that class was removed entirely in 1.37.
2853 $this->clear();
2854 }
2855
2860 public function getNamespace(): int {
2861 return $this->getTitle()->getNamespace();
2862 }
2863
2868 public function getDBkey(): string {
2869 return $this->getTitle()->getDBkey();
2870 }
2871
2876 public function getWikiId() {
2877 return $this->getTitle()->getWikiId();
2878 }
2879
2884 public function canExist(): bool {
2885 return true;
2886 }
2887
2892 public function __toString(): string {
2893 return $this->mTitle->__toString();
2894 }
2895
2900 public function isSamePageAs( PageReference $other ): bool {
2901 // NOTE: keep in sync with PageReferenceValue::isSamePageAs()!
2902 return $this->getWikiId() === $other->getWikiId()
2903 && $this->getNamespace() === $other->getNamespace()
2904 && $this->getDBkey() === $other->getDBkey();
2905 }
2906
2919 // TODO: replace individual member fields with a PageRecord instance that is always present
2920
2921 if ( !$this->mDataLoaded ) {
2922 $this->loadPageData();
2923 }
2924
2925 Assert::precondition(
2926 $this->exists(),
2927 'This WikiPage instance does not represent an existing page: ' . $this->mTitle
2928 );
2929
2930 return new PageStoreRecord(
2931 (object)[
2932 'page_id' => $this->getId(),
2933 'page_namespace' => $this->mTitle->getNamespace(),
2934 'page_title' => $this->mTitle->getDBkey(),
2935 'page_latest' => $this->mLatest,
2936 'page_is_new' => $this->mIsNew ? 1 : 0,
2937 'page_is_redirect' => $this->mPageIsRedirectField ? 1 : 0,
2938 'page_touched' => $this->getTouched(),
2939 'page_lang' => $this->getLanguage()
2940 ],
2941 PageIdentity::LOCAL
2942 );
2943 }
2944
2948 private function getConnectionProvider(): \Wikimedia\Rdbms\IConnectionProvider {
2949 return MediaWikiServices::getInstance()->getConnectionProvider();
2950 }
2951
2952}
2953
2955class_alias( WikiPage::class, 'WikiPage' );
const EDIT_UPDATE
Article is assumed to be pre-existing, fail if it doesn't exist.
Definition Defines.php:117
const NS_FILE
Definition Defines.php:57
const NS_MEDIAWIKI
Definition Defines.php:59
const NS_USER_TALK
Definition Defines.php:54
const EDIT_SILENT
Do not notify other users (e.g.
Definition Defines.php:123
const EDIT_MINOR
Mark this edit minor, if the user is allowed to do so.
Definition Defines.php:120
const NS_CATEGORY
Definition Defines.php:65
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.
wfRandom()
Get a random decimal value in the domain of [0, 1), in a way not likely to give duplicate values for ...
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
const DB_REPLICA
Definition defines.php:26
const DB_PRIMARY
Definition defines.php:28
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
Displays information about a page.
Category objects are immutable, strictly speaking.
Definition Category.php:29
Value object for a comment stored by CommentStore.
Base class for content handling.
Defer callable updates to run later in the PHP process.
Represents information returned by WikiPage::prepareContentForEdit()
Job to purge the HTML/file cache for all pages that link to or use another page or file.
Job to update link tables for rerendered wiki pages.
Create PSR-3 logger objects.
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.
getMainConfig()
Returns the Config object that provides configuration for MediaWiki core.
static getInstance()
Returns the global default instance of the top level service locator.
Domain event representing changes to page protection (aka restriction levels).
Immutable data record representing an editable page on a wiki.
Base representation for an editable wiki page.
Definition WikiPage.php:82
int $mDataLoadedFrom
One of the READ_* constants.
Definition WikiPage.php:132
getMinorEdit()
Returns true if last revision was marked as "minor edit".
Definition WikiPage.php:876
getUser( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Definition WikiPage.php:800
loadLastEdit()
Loads everything except the text This isn't necessary for all uses, so it's only done if needed.
Definition WikiPage.php:693
isSamePageAs(PageReference $other)
Checks whether the given PageReference refers to the same page as this PageReference....
loadFromRow( $data, $from)
Load the object from a database row.
Definition WikiPage.php:484
getParserOutput(?ParserOptions $parserOptions=null, $oldid=null, $noCache=false)
Get a ParserOutput for the given ParserOptions and revision ID.
loadPageData( $from='fromdb')
Load the object from a given source by title.
Definition WikiPage.php:410
getContentHandler()
Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
Definition WikiPage.php:241
updateRevisionOn( $dbw, RevisionRecord $revision, $lastRevision=null, $lastRevIsRedirect=null)
Update the page record to point to a newly saved revision.
getContent( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Get the content of the current revision.
Definition WikiPage.php:761
clear()
Clear the object.
Definition WikiPage.php:258
__construct(PageIdentity $pageIdentity)
Definition WikiPage.php:164
getRevisionRecord()
Get the latest revision.
Definition WikiPage.php:743
static makeParserOptionsFromTitleAndModel(PageReference $pageRef, string $contentModel, $context)
Create canonical parser options for a given title and content model.
__toString()
Returns an informative human readable unique representation of the page identity, for use as a cache ...
getWikiDisplayName()
The display name for the site this content come from.
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, UserIdentity $user, $tags=[])
Update the article's restriction field, and leave a log entry.
static onArticleEdit(Title $title, ?RevisionRecord $revRecord=null, $slotsChanged=null, $maybeRedirectChanged=true)
Purge caches on page update etc.
int false $mLatest
False means "not loaded".
Definition WikiPage.php:116
string $mTimestamp
Timestamp of the current revision or empty string if not loaded.
Definition WikiPage.php:142
getLatest( $wikiId=self::LOCAL)
Get the page_latest field.
Definition WikiPage.php:680
getRedirectURL( $rt)
Get the Title object or URL to use for a redirect.
Definition WikiPage.php:995
isRedirect()
Is the page a redirect, according to secondary tracking tables? If this is true, getRedirectTarget() ...
Definition WikiPage.php:563
pageDataFromId( $dbr, $id, $options=[])
Fetch a page record matching the requested ID.
Definition WikiPage.php:394
insertOn( $dbw, $pageId=null)
Insert a new empty page record for this article.
supportsSections()
Returns true if this page's content model supports sections.
pageData( $dbr, $conditions, $options=[])
Fetch a page record with the given conditions.
Definition WikiPage.php:343
clearPreparedEdit()
Clear the mPreparedEdit cache field, as may be needed by mutable content types.
Definition WikiPage.php:290
getTitle()
Get the title object of the article.
Definition WikiPage.php:250
getRedirectTarget()
If this page is a redirect, get its target.
Definition WikiPage.php:962
getDBkey()
Get the page title in DB key form.This should always return a valid DB key.string
isBatchedDelete( $safetyMargin=0)
Determines if deletion of this page would be batched (executed over time by the job queue) or not (co...
protectDescription(array $limit, array $expiry)
Builds the description to serve as comment for the edit.
doViewUpdates(Authority $performer, $oldid=0, ?RevisionRecord $oldRev=null)
Do standard deferred updates after page view (existing or missing page)
getCategories()
Returns a list of categories this page is a member of.
__clone()
Makes sure that the mTitle object is cloned to the newly cloned WikiPage.
Definition WikiPage.php:180
isNew()
Tests if the page is new (only has one revision).
Definition WikiPage.php:581
followRedirect()
Get the Title object or URL this page redirects to.
Definition WikiPage.php:984
static convertSelectType( $type)
Convert 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants.
Definition WikiPage.php:190
static onArticleCreate(Title $title, $maybeIsRedirect=true)
The onArticle*() functions are supposed to be a kind of hooks which should be called whenever any of ...
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Should the parser cache be used?
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
getCreator( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Get the User object of the user who created the page.
Definition WikiPage.php:821
clearCacheFields()
Clear the object cache fields.
Definition WikiPage.php:269
insertRedirectEntry(LinkTarget $rt, $oldLatest=null)
Insert or update the redirect table entry for this page to indicate it redirects to $rt.
Definition WikiPage.php:974
updateParserCache(array $options=[])
Update the parser cache.
doDeleteArticleReal( $reason, UserIdentity $deleter, $suppress=false, $u1=null, &$error='', $u2=null, $tags=[], $logsubtype='delete', $immediate=false)
Back-end article deletion Deletes the article with database consistency, writes logs,...
getTouched()
Get the page_touched field.
Definition WikiPage.php:646
checkTouched()
Loads page_touched and returns a value indicating if it should be used.
Definition WikiPage.php:638
newPageUpdater( $performer, ?RevisionSlotsUpdate $forUpdate=null)
Returns a PageUpdater for creating new revisions on this page (or creating the page).
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (such as updating link tables).
getId( $wikiId=self::LOCAL)
Definition WikiPage.php:526
getHiddenCategories()
Returns a list of hidden categories this page is a member of.
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Opportunistically enqueue link update jobs after a fresh parser output was generated.
toPageRecord()
Returns the page represented by this WikiPage as a PageStoreRecord.
doEditUpdates(RevisionRecord $revisionRecord, UserIdentity $user, array $options=[])
Do standard deferred updates after page edit.
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
pageDataFromTitle( $dbr, $title, $recency=IDBAccessObject::READ_NORMAL)
Fetch a page record matching the Title object's namespace and title using a sanitized title string.
Definition WikiPage.php:370
__wakeup()
Ensure consistency when unserializing.
static hasDifferencesOutsideMainSlot(RevisionRecord $a, RevisionRecord $b)
Helper method for checking whether two revisions have differences that go beyond the main slot.
getNamespace()
Returns the page's namespace number.The value returned by this method should represent a valid namesp...
getContentModel()
Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
Definition WikiPage.php:599
isLocal()
Whether this content displayed on this page comes from the local database.
PreparedEdit false $mPreparedEdit
Map of cache fields (text, parser output, etc.) for a proposed/new edit.
Definition WikiPage.php:122
insertNullProtectionRevision(string $revCommentMsg, array $limit, array $expiry, bool $cascade, string $reason, UserIdentity $user)
Insert a new dummy revision (aka null revision) for this page, to mark a change in page protection.
getUserText( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Definition WikiPage.php:840
getContributors()
Get a list of users who have edited this article, not including the user who made the most recent rev...
static onArticleDelete(Title $title)
Clears caches when article is deleted.
isCountable( $editInfo=false)
Whether the page may count towards the the site's number of "articles".
Definition WikiPage.php:901
wasLoadedFrom( $from)
Checks whether the page data was loaded using the given database access mode (or better).
Definition WikiPage.php:458
doUserEditContent(Content $content, Authority $performer, $summary, $flags=0, $originalRevId=false, $tags=[], $undidRevId=0)
Change an existing article or create a new article.
doPurge()
Perform the actions of a page purging.
getCurrentUpdate()
Get the state of an ongoing update, shortly before or just after it is saved to the database.
getSourceURL()
Get the source URL for the content on this page, typically the canonical URL, but may be a remote lin...
prepareContentForEdit(Content $content, ?RevisionRecord $revision, UserIdentity $user, $serialFormat=null, $useStash=true)
Prepare content which is about to be saved.
getAutoDeleteReason(&$hasHistory=false)
Auto-generates a deletion reason.
protectDescriptionLog(array $limit, array $expiry)
Builds the description to serve as comment for the log entry.
getComment( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Definition WikiPage.php:861
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new page object.
Definition WikiPage.php:307
setTimestamp( $ts)
Set the page timestamp (use only to avoid DB queries)
Definition WikiPage.php:786
lockAndGetLatest()
Lock the page row for this title+id and return page_latest (or 0)
getLinksTimestamp()
Get the page_links_updated field.
Definition WikiPage.php:668
checkFlags( $flags)
Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
replaceSectionAtRev( $sectionId, Content $sectionContent, $sectionTitle='', $baseRevId=null)
hasViewableContent()
Check if this page is something we're going to be showing some sort of sensible content for.
Definition WikiPage.php:553
Set options of the Parser.
ParserOutput is a rendering of a Content object or a message.
hasReducedExpiry()
Check whether the cache TTL was lowered from the site default.
getOutputFlag(ParserOutputFlags|string $name)
Provides a uniform interface to various boolean flags stored in the ParserOutput.
getCacheExpiry()
Returns the number of seconds after which this object should expire.
Utility class for creating and reading rows in the recentchanges table.
Page revision base class.
getContent( $role, $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Returns the Content of the given slot of this revision.
getUser( $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Fetch revision's author's user identity, if it's available to the specified audience.
getSlots()
Returns the slots defined for this revision.
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
getMainContentModel()
Returns the content model of the main slot of this revision.
getId( $wikiId=self::LOCAL)
Get revision ID.
Service for looking up page revisions.
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
A handle for managing updates for derived page data on edit, import, purge, etc.
Object for storing information about the effects of an edit.
Status object representing the outcome of a page update.
A factory for PageUpdater and DerivedPageDataUpdater instances.
Controller-like object for creating and updating pages by creating new revisions.
Value object representing a modification of revision slots.
Represents a title within MediaWiki.
Definition Title.php:70
getOtherPage()
Get the other title for this page, if this is a subject page get the talk page, if it is a subject pa...
Definition Title.php:1718
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition Title.php:3351
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1038
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:1011
Class to walk into a list of User objects.
Definition UserArray.php:19
User class for the MediaWiki software.
Definition User.php:110
Library for creating and parsing MW-style timestamps.
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:19
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
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', 'sodipodi'=> ' $path/sodipodi -z -w $width -f $input -e $output', 'inkscape'=> ' $path/inkscape -z -w $width -f $input -e $output', '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', 'imgserv'=> ' $path/imgserv-wrapper -i svg -o png -w$width $input $output', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> false, '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, 250, 300,], '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, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'EnableSpecialMute'=> false, 'EnableUserEmailMuteList'=> false, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, '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'=> 'SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> '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, 'BlockTargetMigrationStage' => 768, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], '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, 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', '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', 'BlockTargetMigrationStage' => 'integer', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', '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', ], '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', 'RCEngines' => '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', ], 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', ], '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', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], 'required' => [ 'url', ], ], ], '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.', ],]
Content objects represent page content, e.g.
Definition Content.php:28
getRedirectTarget()
Get the redirect destination or null if this content doesn't represent a redirect.
isCountable( $hasLinks=null)
Whether this content may count towards a "real" wiki page.
getSize()
Get the content's nominal size in "bogo-bytes".
isRedirect()
Whether this Content represents a redirect.
Interface for objects which can provide a MediaWiki context on request.
assertWiki( $wikiId)
Throws if $wikiId is different from the return value of getWikiId().
const LOCAL
Wiki ID value to use with instances that are defined relative to the local wiki.
Represents the target of a wiki link.
Data record representing a page that currently exists as an editable page on a wiki.
Interface for objects (potentially) representing an editable wiki page.
Data record representing a page that is (or used to be, or could be) an editable page on a wiki.
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition Page.php:18
This interface represents the authority associated with the current execution context,...
Definition Authority.php:23
isAllowed(string $permission, ?PermissionStatus $status=null)
Checks whether this authority has the given permission in general.
getUser()
Returns the performer of the actions associated with this authority.
authorizeWrite(string $action, PageIdentity $target, ?PermissionStatus $status=null)
Authorize write access.
Constants for representing well known causes for page updates.
An object representing a page update during an edit.
Interface for objects representing user identity.
getId( $wikiId=self::LOCAL)
Provide primary and replica IDatabase connections.
Interface for database access objects.
Interface to a relational database.
Definition IDatabase.php:31
This class is a delegate to ILBFactory for a given database cluster.
A database connection without write operations.
$source