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;
19use MediaWiki\Deferred\LinksUpdate\CategoryLinksTable;
22use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
56use RuntimeException;
57use stdClass;
58use Stringable;
59use Wikimedia\Assert\Assert;
60use Wikimedia\Assert\PreconditionException;
62use Wikimedia\NonSerializable\NonSerializableTrait;
69use Wikimedia\Timestamp\TimestampFormat as TS;
70
83class WikiPage implements Stringable, Page, PageRecord {
84 use NonSerializableTrait;
85 use ProtectedHookAccessorTrait;
86 use WikiAwareEntityTrait;
87
88 // Constants for $mDataLoadedFrom and related
89
94 protected $mTitle;
95
100 protected $mDataLoaded = false;
101
106 private $mPageIsRedirectField = false;
107
111 private $mIsNew = false;
112
117 protected $mLatest = false;
118
123 protected $mPreparedEdit = false;
124
128 protected $mId = null;
129
133 protected $mDataLoadedFrom = IDBAccessObject::READ_NONE;
134
138 private $mLastRevision = null;
139
143 protected $mTimestamp = '';
144
148 protected $mTouched = '19700101000000';
149
153 protected $mLanguage = null;
154
158 protected $mLinksUpdated = '19700101000000';
159
163 private $derivedDataUpdater = null;
164
165 public function __construct( PageIdentity $pageIdentity ) {
166 $pageIdentity->assertWiki( PageIdentity::LOCAL );
167
168 // TODO: remove the need for casting to Title.
169 $title = Title::newFromPageIdentity( $pageIdentity );
170 if ( !$title->canExist() ) {
171 throw new InvalidArgumentException( "WikiPage constructed on a Title that cannot exist as a page: $title" );
172 }
173
174 $this->mTitle = $title;
175 }
176
181 public function __clone() {
182 $this->mTitle = clone $this->mTitle;
183 }
184
191 public static function convertSelectType( $type ) {
192 switch ( $type ) {
193 case 'fromdb':
194 return IDBAccessObject::READ_NORMAL;
195 case 'fromdbmaster':
196 return IDBAccessObject::READ_LATEST;
197 case 'forupdate':
198 return IDBAccessObject::READ_LOCKING;
199 default:
200 // It may already be an integer or whatever else
201 return $type;
202 }
203 }
204
205 private function getPageUpdaterFactory(): PageUpdaterFactory {
206 return MediaWikiServices::getInstance()->getPageUpdaterFactory();
207 }
208
212 private function getRevisionStore() {
214 }
215
219 private function getDBLoadBalancer() {
220 return MediaWikiServices::getInstance()->getDBLoadBalancer();
221 }
222
229 public function getActionOverrides() {
230 return $this->getContentHandler()->getActionOverrides();
231 }
232
242 public function getContentHandler() {
243 $factory = MediaWikiServices::getInstance()->getContentHandlerFactory();
244 return $factory->getContentHandler( $this->getContentModel() );
245 }
246
251 public function getTitle(): Title {
252 return $this->mTitle;
253 }
254
259 public function clear() {
260 $this->mDataLoaded = false;
261 $this->mDataLoadedFrom = IDBAccessObject::READ_NONE;
262
263 $this->clearCacheFields();
264 }
265
270 protected function clearCacheFields() {
271 $this->mId = null;
272 $this->mPageIsRedirectField = false;
273 $this->mLastRevision = null; // Latest revision
274 $this->mTouched = '19700101000000';
275 $this->mLanguage = null;
276 $this->mLinksUpdated = '19700101000000';
277 $this->mTimestamp = '';
278 $this->mIsNew = false;
279 $this->mLatest = false;
280 // T59026: do not clear $this->derivedDataUpdater since getDerivedDataUpdater() already
281 // checks the requested rev ID and content against the cached one. For most
282 // content types, the output should not change during the lifetime of this cache.
283 // Clearing it can cause extra parses on edit for no reason.
284 }
285
291 public function clearPreparedEdit() {
292 $this->mPreparedEdit = false;
293 }
294
308 public static function getQueryInfo() {
309 $pageLanguageUseDB = MediaWikiServices::getInstance()->getMainConfig()->get(
310 MainConfigNames::PageLanguageUseDB );
311
312 $ret = [
313 'tables' => [ 'page' ],
314 'fields' => [
315 'page_id',
316 'page_namespace',
317 'page_title',
318 'page_is_redirect',
319 'page_is_new',
320 'page_random',
321 'page_touched',
322 'page_links_updated',
323 'page_latest',
324 'page_len',
325 'page_content_model',
326 ],
327 'joins' => [],
328 ];
329
330 if ( $pageLanguageUseDB ) {
331 $ret['fields'][] = 'page_lang';
332 }
333
334 return $ret;
335 }
336
344 protected function pageData( $dbr, $conditions, $options = [] ) {
345 $pageQuery = self::getQueryInfo();
346
347 $this->getHookRunner()->onArticlePageDataBefore(
348 $this, $pageQuery['fields'], $pageQuery['tables'], $pageQuery['joins'] );
349
350 $row = $dbr->newSelectQueryBuilder()
351 ->queryInfo( $pageQuery )
352 ->where( $conditions )
353 ->caller( __METHOD__ )
354 ->options( $options )
355 ->fetchRow();
356
357 $this->getHookRunner()->onArticlePageDataAfter( $this, $row );
358
359 return $row;
360 }
361
371 public function pageDataFromTitle( $dbr, $title, $recency = IDBAccessObject::READ_NORMAL ) {
372 if ( !$title->canExist() ) {
373 return false;
374 }
375 $options = [];
376 if ( ( $recency & IDBAccessObject::READ_EXCLUSIVE ) == IDBAccessObject::READ_EXCLUSIVE ) {
377 $options[] = 'FOR UPDATE';
378 } elseif ( ( $recency & IDBAccessObject::READ_LOCKING ) == IDBAccessObject::READ_LOCKING ) {
379 $options[] = 'LOCK IN SHARE MODE';
380 }
381
382 return $this->pageData( $dbr, [
383 'page_namespace' => $title->getNamespace(),
384 'page_title' => $title->getDBkey() ], $options );
385 }
386
395 public function pageDataFromId( $dbr, $id, $options = [] ) {
396 return $this->pageData( $dbr, [ 'page_id' => $id ], $options );
397 }
398
413 public function loadPageData( $from = IDBAccessObject::READ_NORMAL ) {
414 $from = self::convertSelectType( $from );
415 if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
416 // We already have the data from the correct location, no need to load it twice.
417 return;
418 }
419
420 if ( is_int( $from ) ) {
421 $loadBalancer = $this->getDBLoadBalancer();
422 if ( ( $from & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST ) {
423 $index = DB_PRIMARY;
424 } else {
425 $index = DB_REPLICA;
426 }
427 $db = $loadBalancer->getConnection( $index );
428 $data = $this->pageDataFromTitle( $db, $this->mTitle, $from );
429
430 if ( !$data
431 && $index == DB_REPLICA
432 && $loadBalancer->hasReplicaServers()
433 && $loadBalancer->hasOrMadeRecentPrimaryChanges()
434 ) {
435 $from = IDBAccessObject::READ_LATEST;
436 $db = $loadBalancer->getConnection( DB_PRIMARY );
437 $data = $this->pageDataFromTitle( $db, $this->mTitle, $from );
438 }
439 } else {
440 // No idea from where the caller got this data, assume replica DB.
441 $data = $from;
442 $from = IDBAccessObject::READ_NORMAL;
443 }
444
445 $this->loadFromRow( $data, $from );
446 }
447
462 public function wasLoadedFrom( $from ) {
463 $from = self::convertSelectType( $from );
464
465 if ( !is_int( $from ) ) {
466 // No idea from where the caller got this data, assume replica DB.
467 $from = IDBAccessObject::READ_NORMAL;
468 }
469
470 if ( $from <= $this->mDataLoadedFrom ) {
471 return true;
472 }
473
474 return false;
475 }
476
490 public function loadFromRow( $data, $from ) {
491 $from = self::convertSelectType( $from );
492
493 $lc = MediaWikiServices::getInstance()->getLinkCache();
494 $lc->clearLink( $this->mTitle );
495
496 if ( $data ) {
497 $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
498
499 $this->mTitle->loadFromRow( $data );
500 $this->mId = intval( $data->page_id );
501 $this->mTouched = MWTimestamp::convert( TS::MW, $data->page_touched );
502 $this->mLanguage = $data->page_lang ?? null;
503 $this->mLinksUpdated = $data->page_links_updated === null
504 ? null
505 : MWTimestamp::convert( TS::MW, $data->page_links_updated );
506 $this->mPageIsRedirectField = (bool)$data->page_is_redirect;
507 $this->mIsNew = (bool)( $data->page_is_new ?? 0 );
508 $this->mLatest = intval( $data->page_latest );
509 // T39225: $latest may no longer match the cached latest RevisionRecord object.
510 // Double-check the ID of any cached latest RevisionRecord object for consistency.
511 // T400380: since a DB row had to be loaded in, clear the latest RevisionRecord
512 // object if it can from object cache (e.g. it is RevisionStoreCacheRecord).
513 if (
514 $this->mLastRevision && (
515 $from > $this->mDataLoadedFrom ||
516 $this->mLastRevision->getId() != $this->mLatest
517 )
518 ) {
519 $this->mLastRevision = null;
520 $this->mTimestamp = '';
521 }
522 } else {
523 $lc->addBadLinkObj( $this->mTitle );
524
525 $this->mTitle->loadFromRow( false );
526
527 $this->clearCacheFields();
528
529 $this->mId = 0;
530 }
531
532 $this->mDataLoaded = true;
533 $this->mDataLoadedFrom = $from;
534 }
535
541 public function getId( $wikiId = self::LOCAL ): int {
542 $this->assertWiki( $wikiId );
543
544 if ( !$this->mDataLoaded ) {
545 $this->loadPageData();
546 }
547 return $this->mId;
548 }
549
553 public function exists(): bool {
554 if ( !$this->mDataLoaded ) {
555 $this->loadPageData();
556 }
557 return $this->mId > 0;
558 }
559
568 public function hasViewableContent() {
569 return $this->mTitle->isKnown();
570 }
571
578 public function isRedirect() {
579 $this->loadPageData();
580 if ( $this->mPageIsRedirectField ) {
581 return MediaWikiServices::getInstance()->getRedirectLookup()
582 ->getRedirectTarget( $this->getTitle() ) !== null;
583 }
584
585 return false;
586 }
587
596 public function isNew() {
597 if ( !$this->mDataLoaded ) {
598 $this->loadPageData();
599 }
600
601 return $this->mIsNew;
602 }
603
614 public function getContentModel() {
615 if ( $this->exists() ) {
616 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
617
618 return $cache->getWithSetCallback(
619 $cache->makeKey( 'page-content-model', $this->getLatest() ),
620 $cache::TTL_MONTH,
621 function () {
622 $rev = $this->getRevisionRecord();
623 if ( $rev ) {
624 // Look at the revision's actual content model
625 $slot = $rev->getSlot(
626 SlotRecord::MAIN,
627 RevisionRecord::RAW
628 );
629 return $slot->getModel();
630 } else {
631 LoggerFactory::getInstance( 'wikipage' )->warning(
632 'Page exists but has no (visible) revisions!',
633 [
634 'page-title' => $this->mTitle->getPrefixedDBkey(),
635 'page-id' => $this->getId(),
636 ]
637 );
638 return $this->mTitle->getContentModel();
639 }
640 },
641 [ 'pcTTL' => $cache::TTL_PROC_LONG ]
642 );
643 }
644
645 // use the default model for this page
646 return $this->mTitle->getContentModel();
647 }
648
653 public function checkTouched() {
654 return ( $this->exists() && !$this->isRedirect() );
655 }
656
661 public function getTouched() {
662 if ( !$this->mDataLoaded ) {
663 $this->loadPageData();
664 }
665 return $this->mTouched;
666 }
667
671 public function getLanguage() {
672 if ( !$this->mDataLoaded ) {
673 $this->loadLastEdit();
674 }
675
676 return $this->mLanguage;
677 }
678
683 public function getLinksTimestamp() {
684 if ( !$this->mDataLoaded ) {
685 $this->loadPageData();
686 }
687 return $this->mLinksUpdated;
688 }
689
695 public function getLatest( $wikiId = self::LOCAL ) {
696 $this->assertWiki( $wikiId );
697
698 if ( !$this->mDataLoaded ) {
699 $this->loadPageData();
700 }
701 return (int)$this->mLatest;
702 }
703
708 protected function loadLastEdit() {
709 if ( $this->mLastRevision !== null ) {
710 return; // already loaded
711 }
712
713 $latest = $this->getLatest();
714 if ( !$latest ) {
715 return; // page doesn't exist or is missing page_latest info
716 }
717
718 if ( $this->mDataLoadedFrom == IDBAccessObject::READ_LOCKING ) {
719 // T39225: if session S1 loads the page row FOR UPDATE, the result always
720 // includes the latest changes committed. This is true even within REPEATABLE-READ
721 // transactions, where S1 normally only sees changes committed before the first S1
722 // SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
723 // may not find it since a page row UPDATE and revision row INSERT by S2 may have
724 // happened after the first S1 SELECT.
725 // https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html#isolevel_repeatable-read
726 $revision = $this->getRevisionStore()
727 ->getRevisionByPageId( $this->getId(), $latest, IDBAccessObject::READ_LOCKING );
728 } elseif ( $this->mDataLoadedFrom == IDBAccessObject::READ_LATEST ) {
729 // Bug T93976: if page_latest was loaded from the primary DB, fetch the
730 // revision from there as well, as it may not exist yet on a replica DB.
731 // Also, this keeps the queries in the same REPEATABLE-READ snapshot.
732 $revision = $this->getRevisionStore()
733 ->getRevisionByPageId( $this->getId(), $latest, IDBAccessObject::READ_LATEST );
734 } else {
735 $revision = $this->getRevisionStore()->getKnownLatestRevision( $this->getTitle(), $latest );
736 }
737
738 if ( $revision ) {
739 $this->setLastEdit( $revision );
740 }
741 }
742
746 private function setLastEdit( RevisionRecord $revRecord ) {
747 $this->mLastRevision = $revRecord;
748 $this->mLatest = $revRecord->getId();
749 $this->mTimestamp = $revRecord->getTimestamp();
750 $this->mTouched = max( $this->mTouched, $revRecord->getTimestamp() );
751 }
752
758 public function getRevisionRecord() {
759 $this->loadLastEdit();
760 return $this->mLastRevision;
761 }
762
776 public function getContent( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
777 $this->loadLastEdit();
778 if ( $this->mLastRevision ) {
779 return $this->mLastRevision->getContent( SlotRecord::MAIN, $audience, $performer );
780 }
781 return null;
782 }
783
787 public function getTimestamp() {
788 // Check if the field has been filled by WikiPage::setTimestamp()
789 if ( !$this->mTimestamp ) {
790 $this->loadLastEdit();
791 }
792
793 return MWTimestamp::convert( TS::MW, $this->mTimestamp );
794 }
795
801 public function setTimestamp( $ts ) {
802 $this->mTimestamp = MWTimestamp::convert( TS::MW, $ts );
803 }
804
815 public function getUser( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
816 $this->loadLastEdit();
817 if ( $this->mLastRevision ) {
818 $revUser = $this->mLastRevision->getUser( $audience, $performer );
819 return $revUser ? $revUser->getId() : 0;
820 } else {
821 return -1;
822 }
823 }
824
836 public function getCreator( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
837 $revRecord = $this->getRevisionStore()->getFirstRevision( $this->getTitle() );
838 if ( $revRecord ) {
839 return $revRecord->getUser( $audience, $performer );
840 } else {
841 return null;
842 }
843 }
844
855 public function getUserText( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
856 $this->loadLastEdit();
857 if ( $this->mLastRevision ) {
858 $revUser = $this->mLastRevision->getUser( $audience, $performer );
859 return $revUser ? $revUser->getName() : '';
860 } else {
861 return '';
862 }
863 }
864
876 public function getComment( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
877 $this->loadLastEdit();
878 if ( $this->mLastRevision ) {
879 $revComment = $this->mLastRevision->getComment( $audience, $performer );
880 return $revComment ? $revComment->text : '';
881 } else {
882 return '';
883 }
884 }
885
891 public function getMinorEdit() {
892 $this->loadLastEdit();
893 if ( $this->mLastRevision ) {
894 return $this->mLastRevision->isMinor();
895 } else {
896 return false;
897 }
898 }
899
916 public function isCountable( $editInfo = false ) {
917 $mwServices = MediaWikiServices::getInstance();
918 $articleCountMethod = $mwServices->getMainConfig()->get( MainConfigNames::ArticleCountMethod );
919
920 // NOTE: Keep in sync with DerivedPageDataUpdater::isCountable.
921
922 if ( !$this->mTitle->isContentPage() ) {
923 return false;
924 }
925
926 if ( $editInfo instanceof PreparedEdit ) {
927 // NOTE: only the main slot can make a page a redirect
928 $content = $editInfo->pstContent;
929 } elseif ( $editInfo instanceof PreparedUpdate ) {
930 // NOTE: only the main slot can make a page a redirect
931 $content = $editInfo->getRawContent( SlotRecord::MAIN );
932 } else {
933 $content = $this->getContent();
934 }
935
936 if ( !$content || $content->isRedirect() ) {
937 return false;
938 }
939
940 $hasLinks = null;
941
942 if ( $articleCountMethod === 'link' ) {
943 // nasty special case to avoid re-parsing to detect links
944
945 if ( $editInfo ) {
946 $hasLinks = $editInfo->output->hasLinks();
947 } else {
948 // NOTE: keep in sync with RevisionRenderer::getLinkCount
949 // NOTE: keep in sync with DerivedPageDataUpdater::isCountable
950 $dbr = $mwServices
951 ->getConnectionProvider()
952 ->getReplicaDatabase( PageLinksTable::VIRTUAL_DOMAIN );
953 $hasLinks = (bool)$dbr->newSelectQueryBuilder()
954 ->select( '1' )
955 ->from( 'pagelinks' )
956 ->where( [ 'pl_from' => $this->getId() ] )
957 ->caller( __METHOD__ )->fetchField();
958 }
959 }
960
961 // TODO: MCR: determine $hasLinks for each slot, and use that info
962 // with that slot's Content's isCountable method. That requires per-
963 // slot ParserOutput in the ParserCache, or per-slot info in the
964 // pagelinks table.
965 return $content->isCountable( $hasLinks );
966 }
967
978 public function getRedirectTarget() {
979 wfDeprecated( __METHOD__, '1.38' );
980 $target = MediaWikiServices::getInstance()->getRedirectLookup()->getRedirectTarget( $this );
981 return Title::castFromLinkTarget( $target );
982 }
983
992 public function insertRedirectEntry( LinkTarget $rt, $oldLatest = null ) {
993 wfDeprecated( '1.43', __METHOD__ );
994 return MediaWikiServices::getInstance()->getRedirectStore()
995 ->updateRedirectTarget( $this, $rt );
996 }
997
1003 public function followRedirect() {
1004 $target = MediaWikiServices::getInstance()->getRedirectLookup()->getRedirectTarget( $this );
1005 return $this->getRedirectURL( Title::castFromLinkTarget( $target ) );
1006 }
1007
1015 public function getRedirectURL( $rt ) {
1016 if ( !$rt ) {
1017 return false;
1018 }
1019
1020 if ( $rt->isExternal() ) {
1021 if ( $rt->isLocal() ) {
1022 // Offsite wikis need an HTTP redirect.
1023 // This can be hard to reverse and may produce loops,
1024 // so they may be disabled in the site configuration.
1025 $source = $this->mTitle->getFullURL( 'redirect=no' );
1026 return $rt->getFullURL( [ 'rdfrom' => $source ] );
1027 } else {
1028 // External pages without "local" bit set are not valid
1029 // redirect targets
1030 return false;
1031 }
1032 }
1033
1034 if ( $rt->isSpecialPage() ) {
1035 // Gotta handle redirects to special pages differently:
1036 // Fill the HTTP response "Location" header and ignore the rest of the page we're on.
1037 // Some pages are not valid targets.
1038 if ( $rt->isValidRedirectTarget() ) {
1039 return $rt->getFullURL();
1040 } else {
1041 return false;
1042 }
1043 } elseif ( !$rt->isValidRedirectTarget() ) {
1044 // We somehow got a bad redirect target into the database (T278367)
1045 return false;
1046 }
1047
1048 return $rt;
1049 }
1050
1056 public function getContributors() {
1057 // @todo: This is expensive; cache this info somewhere.
1058
1059 $services = MediaWikiServices::getInstance();
1060 $dbr = $services->getConnectionProvider()->getReplicaDatabase();
1061 $actorNormalization = $services->getActorNormalization();
1062 $userIdentityLookup = $services->getUserIdentityLookup();
1063
1064 $user = $this->getUser()
1065 ? User::newFromId( $this->getUser() )
1066 : User::newFromName( $this->getUserText(), false );
1067
1068 $res = $dbr->newSelectQueryBuilder()
1069 ->select( [
1070 'user_id' => 'actor_user',
1071 'user_name' => 'actor_name',
1072 'actor_id' => 'MIN(rev_actor)',
1073 'user_real_name' => 'MIN(user_real_name)',
1074 'timestamp' => 'MAX(rev_timestamp)',
1075 ] )
1076 ->from( 'revision' )
1077 ->join( 'actor', null, 'rev_actor = actor_id' )
1078 ->leftJoin( 'user', null, 'actor_user = user_id' )
1079 ->where( [
1080 'rev_page' => $this->getId(),
1081 // The user who made the top revision gets credited as "this page was last edited by
1082 // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
1083 $dbr->expr( 'rev_actor', '!=', $actorNormalization->findActorId( $user, $dbr ) ),
1084 // Username hidden?
1085 $dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0',
1086 ] )
1087 ->groupBy( [ 'actor_user', 'actor_name' ] )
1088 ->orderBy( 'timestamp', SelectQueryBuilder::SORT_DESC )
1089 ->caller( __METHOD__ )
1090 ->fetchResultSet();
1091 return new UserArrayFromResult( $res );
1092 }
1093
1103 public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
1104 wfDeprecated( __METHOD__, '1.47' );
1105 // NOTE: Keep in sync with ParserOutputAccess::shouldUseCache().
1106 return $this->exists()
1107 && ( $oldId === null || $oldId === 0 || $oldId === $this->getLatest() )
1108 && $this->getContentHandler()->isParserCacheSupported();
1109 }
1110
1130 public function getParserOutput(
1131 ?ParserOptions $parserOptions = null, $oldid = null, $noCache = false,
1132 array $options = [], array &$errors = []
1133 ) {
1134 if ( $oldid instanceof RevisionRecord ) {
1135 $revision = $oldid;
1136 } elseif ( $oldid ) {
1137 $revision = $this->getRevisionStore()->getRevisionByTitle( $this->getTitle(), $oldid );
1138
1139 if ( !$revision ) {
1140 return false;
1141 }
1142 } else {
1143 $revision = $this->getRevisionRecord();
1144 }
1145
1146 if ( !$parserOptions ) {
1147 $parserOptions = ParserOptions::newFromAnon();
1148 }
1149
1150 if ( $noCache ) {
1151 $options += [ ParserOutputAccess::OPT_NO_CACHE => true ];
1152 }
1153
1154 $status = MediaWikiServices::getInstance()->getParserOutputAccess()->getParserOutput(
1155 $this, $parserOptions, $revision, $options
1156 );
1157 if ( $status->isOK() ) {
1158 return $status->getValue();
1159 } else {
1160 $errors = [ ...$status->getMessages() ];
1161 // convert null to false
1162 return false;
1163 }
1164 }
1165
1173 public function doViewUpdates(
1174 Authority $performer,
1175 $oldRev = null,
1176 $oldRevDeprecated = null
1177 ) {
1178 if ( func_num_args() > 2 ) {
1179 wfDeprecatedMsg( 'Passing $oldid to ' . __METHOD__ . ' is deprecated since 1.46.' );
1180 $oldRev = $oldRevDeprecated;
1181 }
1182
1183 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
1184 return;
1185 }
1186
1187 DeferredUpdates::addCallableUpdate(
1188 function () use ( $performer ) {
1189 // In practice, these hook handlers simply debounce into a post-send
1190 // to do their work since none of the use cases for this hook require
1191 // a blocking pre-send callback.
1192 //
1193 // TODO: Move this hook to post-send.
1194 //
1195 // For now, it is unofficially possible for an extension to use
1196 // onPageViewUpdates to try to insert JavaScript via global $wgOut.
1197 // This isn't supported (the hook doesn't pass OutputPage), and
1198 // can't be since OutputPage may be disabled or replaced on some
1199 // pages that we do support page view updates for. We also run
1200 // this hook after HTMLFileCache, which also naturally can't
1201 // support modifying OutputPage. Handlers that modify the page
1202 // may use onBeforePageDisplay instead, which runs behind
1203 // HTMLFileCache and won't run on non-OutputPage responses.
1204 $legacyUser = MediaWikiServices::getInstance()
1205 ->getUserFactory()
1206 ->newFromAuthority( $performer );
1207 $this->getHookRunner()->onPageViewUpdates( $this, $legacyUser );
1208 },
1209 DeferredUpdates::PRESEND
1210 );
1211
1212 // Update newtalk and watchlist notification status
1213 MediaWikiServices::getInstance()
1214 ->getWatchlistManager()
1215 ->clearTitleUserNotifications( $performer, $this, $oldRev );
1216 }
1217
1224 public function doPurge() {
1225 if ( !$this->getHookRunner()->onArticlePurge( $this ) ) {
1226 return false;
1227 }
1228
1229 $this->mTitle->invalidateCache();
1230
1231 // Clear file cache and send purge after above page_touched update was committed
1232 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
1233 $hcu->purgeTitleUrls( $this->mTitle, $hcu::PURGE_PRESEND );
1234
1235 if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI ) {
1236 MediaWikiServices::getInstance()->getMessageCache()
1237 ->updateMessageOverride( $this->mTitle, $this->getContent() );
1238 }
1239 InfoAction::invalidateCache( $this->mTitle, $this->getLatest() );
1240
1241 return true;
1242 }
1243
1262 public function insertOn( $dbw, $pageId = null ) {
1263 $pageIdForInsert = $pageId ? [ 'page_id' => $pageId ] : [];
1264 $row = [
1265 'page_namespace' => $this->mTitle->getNamespace(),
1266 'page_title' => $this->mTitle->getDBkey(),
1267 'page_is_redirect' => 0, // Will set this shortly...
1268 'page_is_new' => 1,
1269 'page_random' => wfRandom(),
1270 'page_touched' => $dbw->timestamp(),
1271 'page_latest' => 0, // Fill this in shortly...
1272 'page_len' => 0, // Fill this in shortly...
1273 ] + $pageIdForInsert;
1274 $dbw->newInsertQueryBuilder()
1275 ->insertInto( 'page' )
1276 ->ignore()
1277 ->row( $row )
1278 ->caller( __METHOD__ )->execute();
1279
1280 if ( $dbw->affectedRows() > 0 ) {
1281 $newid = $pageId ? (int)$pageId : $dbw->insertId();
1282 $this->mId = $newid;
1283 $this->mTitle->resetArticleID( $newid );
1284
1285 // Duplicate the row on secondary links storage if needed but set the page_id
1286 $row['page_id'] = $newid;
1287 $insert = $dbw->newInsertQueryBuilder()
1288 ->insertInto( 'page' )
1289 ->ignore()
1290 ->row( $row )
1291 ->caller( __METHOD__ );
1292 MediaWikiServices::getInstance()->getLinkWriteDuplicator()->duplicate( $insert );
1293
1294 return $newid;
1295 } else {
1296 return false; // nothing changed
1297 }
1298 }
1299
1317 public function updateRevisionOn(
1318 $dbw,
1319 RevisionRecord $revision,
1320 $lastRevision = null,
1321 $lastRevIsRedirect = null
1322 ) {
1323 // TODO: move into PageUpdater or PageStore
1324 // NOTE: when doing that, make sure cached fields get reset in doUserEditContent,
1325 // and in the compat stub!
1326
1327 $revId = $revision->getId();
1328 Assert::parameter( $revId > 0, '$revision->getId()', 'must be > 0' );
1329
1330 $content = $revision->getContent( SlotRecord::MAIN );
1331 $len = $content ? $content->getSize() : 0;
1332 $rt = $content ? $content->getRedirectTarget() : null;
1333 $isNew = $lastRevision === 0;
1334 $isRedirect = $rt !== null;
1335
1336 $conditions = [ 'page_id' => $this->getId() ];
1337
1338 if ( $lastRevision !== null ) {
1339 // An extra check against threads stepping on each other
1340 $conditions['page_latest'] = $lastRevision;
1341 }
1342
1343 $model = $revision->getMainContentModel();
1344
1345 $row = [ /* SET */
1346 'page_latest' => $revId,
1347 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
1348 'page_is_new' => $isNew ? 1 : 0,
1349 'page_is_redirect' => $isRedirect ? 1 : 0,
1350 'page_len' => $len,
1351 'page_content_model' => $model,
1352 ];
1353
1354 $update = $dbw->newUpdateQueryBuilder()
1355 ->update( 'page' )
1356 ->set( $row )
1357 ->where( $conditions )
1358 ->caller( __METHOD__ );
1359 $update->execute();
1360 MediaWikiServices::getInstance()->getLinkWriteDuplicator()->duplicate( $update );
1361
1362 $result = $dbw->affectedRows() > 0;
1363 if ( $result ) {
1364 $insertedRow = $this->pageData( $dbw, [ 'page_id' => $this->getId() ] );
1365
1366 if ( !$insertedRow ) {
1367 throw new RuntimeException( 'Failed to load freshly inserted row' );
1368 }
1369
1370 $this->mTitle->loadFromRow( $insertedRow );
1371 MediaWikiServices::getInstance()->getRedirectStore()
1372 ->updateRedirectTarget( $this, $rt, $lastRevIsRedirect );
1373 $this->setLastEdit( $revision );
1374 $this->mPageIsRedirectField = (bool)$rt;
1375 $this->mIsNew = $isNew;
1376
1377 // Update the LinkCache.
1378 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1379 $linkCache->addGoodLinkObjFromRow(
1380 $this->mTitle,
1381 $insertedRow
1382 );
1383 }
1384
1385 return $result;
1386 }
1387
1401 $aSlots = $a->getSlots();
1402 $bSlots = $b->getSlots();
1403 $changedRoles = $aSlots->getRolesWithDifferentContent( $bSlots );
1404
1405 return ( $changedRoles !== [ SlotRecord::MAIN ] && $changedRoles !== [] );
1406 }
1407
1418 public function supportsSections() {
1419 return $this->getContentHandler()->supportsSections();
1420 }
1421
1435 public function replaceSectionContent(
1436 $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
1437 ) {
1438 $baseRevId = null;
1439 if ( $edittime && $sectionId !== 'new' ) {
1440 $lb = $this->getDBLoadBalancer();
1441 $rev = $this->getRevisionStore()->getRevisionByTimestamp( $this->mTitle, $edittime );
1442 // Try the primary database if this thread may have just added it.
1443 // The logic to fallback to the primary database if the replica is missing
1444 // the revision could be generalized into RevisionStore, but we don't want
1445 // to encourage loading of revisions by timestamp.
1446 if ( !$rev
1447 && $lb->hasReplicaServers()
1448 && $lb->hasOrMadeRecentPrimaryChanges()
1449 ) {
1450 $rev = $this->getRevisionStore()->getRevisionByTimestamp(
1451 $this->mTitle, $edittime, IDBAccessObject::READ_LATEST );
1452 }
1453 if ( $rev ) {
1454 $baseRevId = $rev->getId();
1455 }
1456 }
1457
1458 return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
1459 }
1460
1473 public function replaceSectionAtRev( $sectionId, Content $sectionContent,
1474 $sectionTitle = '', $baseRevId = null
1475 ) {
1476 if ( strval( $sectionId ) === '' ) {
1477 // Whole-page edit; let the whole text through
1478 $newContent = $sectionContent;
1479 } else {
1480 if ( !$this->supportsSections() ) {
1481 throw new BadMethodCallException( "sections not supported for content model " .
1482 $this->getContentHandler()->getModelID() );
1483 }
1484
1485 // T32711: always use current version when adding a new section
1486 if ( $baseRevId === null || $sectionId === 'new' ) {
1487 $oldContent = $this->getContent();
1488 } else {
1489 $revRecord = $this->getRevisionStore()->getRevisionById( $baseRevId );
1490 if ( !$revRecord ) {
1491 wfDebug( __METHOD__ . " asked for bogus section (page: " .
1492 $this->getId() . "; section: $sectionId)" );
1493 return null;
1494 }
1495
1496 $oldContent = $revRecord->getContent( SlotRecord::MAIN );
1497 }
1498
1499 if ( !$oldContent ) {
1500 wfDebug( __METHOD__ . ": no page text" );
1501 return null;
1502 }
1503
1504 $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
1505 }
1506
1507 return $newContent;
1508 }
1509
1520 public function checkFlags( $flags ) {
1521 wfDeprecated( __METHOD__, '1.32' );
1522 if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
1523 if ( $this->exists() ) {
1524 $flags |= EDIT_UPDATE;
1525 } else {
1526 $flags |= EDIT_NEW;
1527 }
1528 }
1529
1530 return $flags;
1531 }
1532
1560 private function getDerivedDataUpdater(
1561 ?UserIdentity $forUser = null,
1562 ?RevisionRecord $forRevision = null,
1563 ?RevisionSlotsUpdate $forUpdate = null,
1564 $forEdit = false
1565 ) {
1566 if ( !$forRevision && !$forUpdate ) {
1567 // NOTE: can't re-use an existing derivedDataUpdater if we don't know what the caller is
1568 // going to use it with.
1569 $this->derivedDataUpdater = null;
1570 }
1571
1572 if ( $this->derivedDataUpdater && !$this->derivedDataUpdater->isContentPrepared() ) {
1573 // NOTE: can't re-use an existing derivedDataUpdater if other code that has a reference
1574 // to it did not yet initialize it, because we don't know what data it will be
1575 // initialized with.
1576 $this->derivedDataUpdater = null;
1577 }
1578
1579 // XXX: It would be nice to have an LRU cache instead of trying to re-use a single instance.
1580 // However, there is no good way to construct a cache key. We'd need to check against all
1581 // cached instances.
1582
1583 if ( $this->derivedDataUpdater
1584 && !$this->derivedDataUpdater->isReusableFor(
1585 $forUser,
1586 $forRevision,
1587 $forUpdate,
1588 $forEdit ? $this->getLatest() : null
1589 )
1590 ) {
1591 $this->derivedDataUpdater = null;
1592 }
1593
1594 if ( !$this->derivedDataUpdater ) {
1595 $this->derivedDataUpdater =
1596 $this->getPageUpdaterFactory()->newDerivedPageDataUpdater( $this );
1597 }
1598
1599 return $this->derivedDataUpdater;
1600 }
1601
1649 public function doUserEditContent(
1650 Content $content,
1651 Authority $performer,
1652 $summary,
1653 $flags = 0,
1654 $originalRevId = false,
1655 $tags = [],
1656 $undidRevId = 0
1657 ): PageUpdateStatus {
1658 $useNPPatrol = MediaWikiServices::getInstance()->getMainConfig()->get(
1659 MainConfigNames::UseNPPatrol );
1660 $useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get(
1661 MainConfigNames::UseRCPatrol );
1662 if ( !( $summary instanceof CommentStoreComment ) ) {
1663 $summary = CommentStoreComment::newUnsavedComment( trim( $summary ) );
1664 }
1665
1666 // TODO: this check is here for backwards-compatibility with 1.31 behavior.
1667 // Checking the minoredit right should be done in the same place the 'bot' right is
1668 // checked for the EDIT_FORCE_BOT flag, which is currently in EditPage::attemptSave.
1669 if ( ( $flags & EDIT_MINOR ) && !$performer->isAllowed( 'minoredit' ) ) {
1670 $flags &= ~EDIT_MINOR;
1671 }
1672
1673 $slotsUpdate = new RevisionSlotsUpdate();
1674 $slotsUpdate->modifyContent( SlotRecord::MAIN, $content );
1675
1676 // NOTE: while doUserEditContent() executes, callbacks to getDerivedDataUpdater and
1677 // prepareContentForEdit will generally use the DerivedPageDataUpdater that is also
1678 // used by this PageUpdater. However, there is no guarantee for this.
1679 $updater = $this->newPageUpdater( $performer->getUser(), $slotsUpdate )
1680 ->setContent( SlotRecord::MAIN, $content )
1681 ->setOriginalRevisionId( $originalRevId );
1682 if ( $undidRevId ) {
1683 $updater->setCause( PageUpdateCauses::CAUSE_UNDO );
1684 $updater->markAsRevert(
1685 EditResult::REVERT_UNDO,
1686 $undidRevId,
1687 $originalRevId ?: null
1688 );
1689 }
1690
1691 $needsPatrol = $useRCPatrol || ( $useNPPatrol && !$this->exists() );
1692
1693 // TODO: this logic should not be in the storage layer, it's here for compatibility
1694 // with 1.31 behavior. Applying the 'autopatrol' right should be done in the same
1695 // place the 'bot' right is handled, which is currently in EditPage::attemptSave.
1696
1697 if ( $needsPatrol && $performer->authorizeWrite( 'autopatrol', $this->getTitle() ) ) {
1698 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
1699 }
1700
1701 $updater->addTags( $tags );
1702
1703 $revRec = $updater->saveRevision(
1704 $summary,
1705 $flags
1706 );
1707
1708 // $revRec will be null if the edit failed, or if no new revision was created because
1709 // the content did not change.
1710 if ( $revRec ) {
1711 // update cached fields
1712 // TODO: this is currently redundant to what is done in updateRevisionOn.
1713 // But updateRevisionOn() should move into PageStore, and then this will be needed.
1714 $this->setLastEdit( $revRec );
1715 }
1716
1717 return $updater->getStatus();
1718 }
1719
1740 public function newPageUpdater( $performer, ?RevisionSlotsUpdate $forUpdate = null ) {
1741 if ( !$performer instanceof UserIdentity && $performer instanceof Authority ) {
1742 wfDeprecatedMsg( __METHOD__ . ' with an Authority as $performer is deprecated', '1.47' );
1743 $performer = $performer->getUser();
1744 }
1745
1746 $pageUpdater = $this->getPageUpdaterFactory()->newPageUpdaterForDerivedPageDataUpdater(
1747 $this,
1748 $performer,
1749 $this->getDerivedDataUpdater( $performer, null, $forUpdate, true )
1750 );
1751
1752 return $pageUpdater;
1753 }
1754
1769 public function makeParserOptions( $context ) {
1770 return self::makeParserOptionsFromTitleAndModel(
1771 $this->getTitle(), $this->getContentModel(), $context
1772 );
1773 }
1774
1784 PageReference $pageRef, string $contentModel, $context
1785 ) {
1786 $options = ParserOptions::newCanonical( $context );
1787
1788 $title = Title::newFromPageReference( $pageRef );
1789 if ( $title->isConversionTable() ) {
1790 // @todo ConversionTable should become a separate content model, so
1791 // we don't need special cases like this one, but see T313455.
1792 $options->disableContentConversion();
1793 }
1794 # Add in the preferred variant from the URL or user preferences
1795 $services = MediaWikiServices::getInstance();
1796 $languageConverterFactory = $services->getLanguageConverterFactory();
1797 if ( !$languageConverterFactory->isConversionDisabled() ) {
1798 $converter = $languageConverterFactory->getLanguageConverter(
1799 $title->getPageLanguage()
1800 );
1801 if ( $converter->hasVariants() ) {
1802 $variant = $services->getLanguageFactory()->getLanguage(
1803 $converter->getPreferredVariant()
1804 );
1805 $options->setVariant( $variant );
1806 }
1807 }
1808
1809 return $options;
1810 }
1811
1832 public function prepareContentForEdit(
1833 Content $content,
1834 ?RevisionRecord $revision,
1835 UserIdentity $user,
1836 $serialFormat = null,
1837 $useStash = true
1838 ) {
1839 $slots = RevisionSlotsUpdate::newFromContent( [ SlotRecord::MAIN => $content ] );
1840 $updater = $this->getDerivedDataUpdater( $user, $revision, $slots );
1841
1842 if ( !$updater->isUpdatePrepared() ) {
1843 $updater->prepareContent( $user, $slots, $useStash );
1844
1845 if ( $revision ) {
1846 $updater->prepareUpdate(
1847 $revision,
1848 [
1849 'causeAction' => 'prepare-edit',
1850 'causeAgent' => $user->getName(),
1851 ]
1852 );
1853 }
1854 }
1855
1856 return $updater->getPreparedEdit();
1857 }
1858
1874 public function doEditUpdates(
1875 RevisionRecord $revisionRecord,
1876 UserIdentity $user,
1877 array $options = []
1878 ) {
1879 wfDeprecated( __METHOD__, '1.32' ); // emitting warnings since 1.44
1880
1881 $options += [
1882 'causeAction' => 'edit-page',
1883 'causeAgent' => $user->getName(),
1884 'emitEvents' => false // prior page state is unknown, can't emit events
1885 ];
1886
1887 $updater = $this->getDerivedDataUpdater( $user, $revisionRecord );
1888
1889 $updater->prepareUpdate( $revisionRecord, $options );
1890
1891 $updater->doUpdates();
1892 }
1893
1906 public function updateParserCache( array $options = [] ) {
1907 $revision = $this->getRevisionRecord();
1908 if ( !$revision || !$revision->getId() ) {
1909 LoggerFactory::getInstance( 'wikipage' )->info(
1910 __METHOD__ . ' called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
1911 );
1912 return;
1913 }
1914 $userIdentity = $revision->getUser( RevisionRecord::RAW );
1915
1916 $updater = $this->getDerivedDataUpdater( $userIdentity, $revision );
1917 $updater->prepareUpdate( $revision, $options );
1918 $updater->doParserCacheUpdate();
1919 }
1920
1950 public function doSecondaryDataUpdates( array $options = [] ) {
1951 $options['recursive'] ??= true;
1952 $revision = $this->getRevisionRecord();
1953 if ( !$revision || !$revision->getId() ) {
1954 LoggerFactory::getInstance( 'wikipage' )->info(
1955 __METHOD__ . ' called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
1956 );
1957 return;
1958 }
1959 $userIdentity = $revision->getUser( RevisionRecord::RAW );
1960
1961 $updater = $this->getDerivedDataUpdater( $userIdentity, $revision );
1962 $updater->prepareUpdate( $revision, $options );
1963 $updater->doSecondaryDataUpdates( $options );
1964 }
1965
1980 public function doUpdateRestrictions( array $limit, array $expiry,
1981 &$cascade, $reason, UserIdentity $user, $tags = []
1982 ) {
1983 $services = MediaWikiServices::getInstance();
1984 $readOnlyMode = $services->getReadOnlyMode();
1985 if ( $readOnlyMode->isReadOnly() ) {
1986 return Status::newFatal( 'readonlytext', $readOnlyMode->getReason() );
1987 }
1988
1989 $this->loadPageData( IDBAccessObject::READ_LATEST );
1990 $restrictionStore = $services->getRestrictionStore();
1991 $restrictionStore->loadRestrictions( $this->mTitle, IDBAccessObject::READ_LATEST );
1992 $restrictionTypes = $restrictionStore->listApplicableRestrictionTypes( $this->mTitle );
1993 $id = $this->getId();
1994
1995 if ( !$cascade ) {
1996 $cascade = false;
1997 }
1998
1999 // Take this opportunity to purge out expired restrictions
2000 Title::purgeExpiredRestrictions();
2001
2002 // @todo: Same limitations as described in ProtectionForm.php (line 37);
2003 // we expect a single selection, but the schema allows otherwise.
2004 $isProtected = false;
2005 $protect = false;
2006 $changed = false;
2007
2008 $dbw = $services->getConnectionProvider()->getPrimaryDatabase();
2009 $restrictionMapBefore = [];
2010 $restrictionMapAfter = [];
2011
2012 foreach ( $restrictionTypes as $action ) {
2013 if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) {
2014 $expiry[$action] = 'infinity';
2015 }
2016
2017 // Get current restrictions on $action
2018 $restrictionMapBefore[$action] = $restrictionStore->getRestrictions( $this->mTitle, $action );
2019 $limit[$action] ??= '';
2020
2021 if ( $limit[$action] === '' ) {
2022 $restrictionMapAfter[$action] = [];
2023 } else {
2024 $protect = true;
2025 $restrictionMapAfter[$action] = explode( ',', $limit[$action] );
2026 }
2027
2028 $current = implode( ',', $restrictionMapBefore[$action] );
2029 if ( $current != '' ) {
2030 $isProtected = true;
2031 }
2032
2033 if ( $limit[$action] != $current ) {
2034 $changed = true;
2035 } elseif ( $limit[$action] != '' ) {
2036 // Only check expiry change if the action is actually being
2037 // protected, since expiry does nothing on an not-protected
2038 // action.
2039 if ( $restrictionStore->getRestrictionExpiry( $this->mTitle, $action ) != $expiry[$action] ) {
2040 $changed = true;
2041 }
2042 }
2043 }
2044
2045 if ( !$changed && $protect && $restrictionStore->areRestrictionsCascading( $this->mTitle ) != $cascade ) {
2046 $changed = true;
2047 }
2048
2049 // If nothing has changed, do nothing
2050 if ( !$changed ) {
2051 return Status::newGood();
2052 }
2053
2054 if ( !$protect ) { // No protection at all means unprotection
2055 $revCommentMsg = 'unprotectedarticle-comment';
2056 $logAction = 'unprotect';
2057 } elseif ( $isProtected ) {
2058 $revCommentMsg = 'modifiedarticleprotection-comment';
2059 $logAction = 'modify';
2060 } else {
2061 $revCommentMsg = 'protectedarticle-comment';
2062 $logAction = 'protect';
2063 }
2064
2065 $logRelationsValues = [];
2066 $logRelationsField = null;
2067 $logParamsDetails = [];
2068
2069 // Null revision (used for change tag insertion)
2070 $dummyRevisionRecord = null;
2071
2072 $legacyUser = $services->getUserFactory()->newFromUserIdentity( $user );
2073 if ( !$this->getHookRunner()->onArticleProtect( $this, $legacyUser, $limit, $reason ) ) {
2074 return Status::newGood();
2075 }
2076
2077 if ( $id ) { // Protection of existing page
2078 // Only certain restrictions can cascade...
2079 $editrestriction = isset( $limit['edit'] )
2080 ? [ $limit['edit'] ]
2081 : $restrictionStore->getRestrictions( $this->mTitle, 'edit' );
2082 foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
2083 $editrestriction[$key] = 'editprotected'; // backwards compatibility
2084 }
2085 foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
2086 $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
2087 }
2088
2089 $cascadingRestrictionLevels = $services->getMainConfig()
2090 ->get( MainConfigNames::CascadingRestrictionLevels );
2091
2092 foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
2093 $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
2094 }
2095 foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
2096 $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
2097 }
2098
2099 // The schema allows multiple restrictions
2100 if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
2101 $cascade = false;
2102 }
2103
2104 // insert dummy revision to identify the page protection change as edit summary
2105 $dummyRevisionRecord = $this->insertNullProtectionRevision(
2106 $revCommentMsg,
2107 $limit,
2108 $expiry,
2109 $cascade,
2110 $reason,
2111 $user
2112 );
2113
2114 if ( $dummyRevisionRecord === null ) {
2115 return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
2116 }
2117
2118 $logRelationsField = 'pr_id';
2119
2120 // T214035: Avoid deadlock on MySQL.
2121 // Do a DELETE by primary key (pr_id) for any existing protection rows.
2122 // On MySQL and derivatives, unconditionally deleting by page ID (pr_page) would.
2123 // place a gap lock if there are no matching rows. This can deadlock when another
2124 // thread modifies protection settings for page IDs in the same gap.
2125 $existingProtectionIds = $dbw->newSelectQueryBuilder()
2126 ->select( 'pr_id' )
2127 ->from( 'page_restrictions' )
2128 ->where( [ 'pr_page' => $id, 'pr_type' => array_map( 'strval', array_keys( $limit ) ) ] )
2129 ->caller( __METHOD__ )->fetchFieldValues();
2130
2131 if ( $existingProtectionIds ) {
2132 $dbw->newDeleteQueryBuilder()
2133 ->deleteFrom( 'page_restrictions' )
2134 ->where( [ 'pr_id' => $existingProtectionIds ] )
2135 ->caller( __METHOD__ )->execute();
2136 }
2137
2138 // Update restrictions table
2139 foreach ( $limit as $action => $restrictions ) {
2140 if ( $restrictions != '' ) {
2141 $cascadeValue = ( $cascade && $action == 'edit' ) ? 1 : 0;
2142 $dbw->newInsertQueryBuilder()
2143 ->insertInto( 'page_restrictions' )
2144 ->row( [
2145 'pr_page' => $id,
2146 'pr_type' => $action,
2147 'pr_level' => $restrictions,
2148 'pr_cascade' => $cascadeValue,
2149 'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
2150 ] )
2151 ->caller( __METHOD__ )->execute();
2152 $logRelationsValues[] = $dbw->insertId();
2153 $logParamsDetails[] = [
2154 'type' => $action,
2155 'level' => $restrictions,
2156 'expiry' => $expiry[$action],
2157 'cascade' => (bool)$cascadeValue,
2158 ];
2159 }
2160 }
2161 } else { // Protection of non-existing page (also known as "title protection")
2162 // Cascade protection is meaningless in this case
2163 $cascade = false;
2164
2165 if ( $limit['create'] != '' ) {
2166 $commentFields = $services->getCommentStore()->insert( $dbw, 'pt_reason', $reason );
2167 $dbw->newReplaceQueryBuilder()
2168 ->table( 'protected_titles' )
2169 ->uniqueIndexFields( [ 'pt_namespace', 'pt_title' ] )
2170 ->rows( [
2171 'pt_namespace' => $this->mTitle->getNamespace(),
2172 'pt_title' => $this->mTitle->getDBkey(),
2173 'pt_create_perm' => $limit['create'],
2174 'pt_timestamp' => $dbw->timestamp(),
2175 'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
2176 'pt_user' => $user->getId(),
2177 ] + $commentFields )
2178 ->caller( __METHOD__ )->execute();
2179 $logParamsDetails[] = [
2180 'type' => 'create',
2181 'level' => $limit['create'],
2182 'expiry' => $expiry['create'],
2183 ];
2184 } else {
2185 $dbw->newDeleteQueryBuilder()
2186 ->deleteFrom( 'protected_titles' )
2187 ->where( [
2188 'pt_namespace' => $this->mTitle->getNamespace(),
2189 'pt_title' => $this->mTitle->getDBkey()
2190 ] )
2191 ->caller( __METHOD__ )->execute();
2192 }
2193 }
2194
2195 $this->getHookRunner()->onArticleProtectComplete( $this, $legacyUser, $limit, $reason );
2196
2197 $restrictionStore->flushRestrictions( $this->mTitle );
2198
2199 InfoAction::invalidateCache( $this->mTitle );
2200
2201 if ( $logAction == 'unprotect' ) {
2202 $params = [];
2203 } else {
2204 $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
2205 $params = [
2206 '4::description' => $protectDescriptionLog, // parameter for IRC
2207 '5:bool:cascade' => $cascade,
2208 'details' => $logParamsDetails, // parameter for localize and api
2209 ];
2210 }
2211
2212 // Update the protection log
2213 $logEntry = new ManualLogEntry( 'protect', $logAction );
2214 $logEntry->setTarget( $this->mTitle );
2215 $logEntry->setComment( $reason );
2216 $logEntry->setPerformer( $user );
2217 $logEntry->setParameters( $params );
2218 if ( $dummyRevisionRecord !== null ) {
2219 $logEntry->setAssociatedRevId( $dummyRevisionRecord->getId() );
2220 }
2221 $logEntry->addTags( $tags );
2222 if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
2223 $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
2224 }
2225 $logId = $logEntry->insert();
2226 $logEntry->publish( $logId );
2227
2228 $event = new PageProtectionChangedEvent(
2229 $this,
2230 $restrictionMapBefore,
2231 $restrictionMapAfter,
2232 $expiry,
2233 $cascade,
2234 $user,
2235 $reason,
2236 $tags
2237 );
2238
2239 $dispatcher = MediaWikiServices::getInstance()->getDomainEventDispatcher();
2240 $dispatcher->dispatch( $event, $services->getConnectionProvider() );
2241
2242 return Status::newGood( $logId );
2243 }
2244
2266 Assert::precondition(
2267 $this->derivedDataUpdater !== null,
2268 'There is no ongoing update tracked by this instance of WikiPage!'
2269 );
2270
2271 return $this->derivedDataUpdater;
2272 }
2273
2289 string $revCommentMsg,
2290 array $limit,
2291 array $expiry,
2292 bool $cascade,
2293 string $reason,
2294 UserIdentity $user
2295 ): ?RevisionRecord {
2296 // Prepare a dummy revision to be added to the history
2297 $editComment = wfMessage(
2298 $revCommentMsg,
2299 $this->mTitle->getPrefixedText(),
2300 $user->getName()
2301 )->inContentLanguage()->text();
2302 if ( $reason ) {
2303 $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
2304 }
2305 $protectDescription = $this->protectDescription( $limit, $expiry );
2306 if ( $protectDescription ) {
2307 $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2308 $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )
2309 ->inContentLanguage()->text();
2310 }
2311 if ( $cascade ) {
2312 $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2313 $editComment .= wfMessage( 'brackets' )->params(
2314 wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
2315 )->inContentLanguage()->text();
2316 }
2317
2318 return $this->newPageUpdater( $user )
2319 ->setCause( PageUpdater::CAUSE_PROTECTION_CHANGE )
2320 ->saveDummyRevision( $editComment, EDIT_SILENT | EDIT_MINOR );
2321 }
2322
2327 protected function formatExpiry( $expiry ) {
2328 if ( $expiry != 'infinity' ) {
2329 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2330 return wfMessage(
2331 'protect-expiring',
2332 $contLang->timeanddate( $expiry, false, false ),
2333 $contLang->date( $expiry, false, false ),
2334 $contLang->time( $expiry, false, false )
2335 )->inContentLanguage()->text();
2336 } else {
2337 return wfMessage( 'protect-expiry-indefinite' )
2338 ->inContentLanguage()->text();
2339 }
2340 }
2341
2349 public function protectDescription( array $limit, array $expiry ) {
2350 $protectDescription = '';
2351
2352 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2353 # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
2354 # All possible message keys are listed here for easier grepping:
2355 # * restriction-create
2356 # * restriction-edit
2357 # * restriction-move
2358 # * restriction-upload
2359 $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
2360 # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
2361 # with '' filtered out. All possible message keys are listed below:
2362 # * protect-level-autoconfirmed
2363 # * protect-level-sysop
2364 $restrictionsText = wfMessage( 'protect-level-' . $restrictions )
2365 ->inContentLanguage()->text();
2366
2367 $expiryText = $this->formatExpiry( $expiry[$action] );
2368
2369 if ( $protectDescription !== '' ) {
2370 $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2371 }
2372 $protectDescription .= wfMessage( 'protect-summary-desc' )
2373 ->params( $actionText, $restrictionsText, $expiryText )
2374 ->inContentLanguage()->text();
2375 }
2376
2377 return $protectDescription;
2378 }
2379
2391 public function protectDescriptionLog( array $limit, array $expiry ) {
2392 $protectDescriptionLog = '';
2393
2394 $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark();
2395 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2396 $expiryText = $this->formatExpiry( $expiry[$action] );
2397 $protectDescriptionLog .=
2398 $dirMark .
2399 "[$action=$restrictions] ($expiryText)";
2400 }
2401
2402 return trim( $protectDescriptionLog );
2403 }
2404
2420 public function isBatchedDelete( $safetyMargin = 0 ) {
2421 wfDeprecated( __METHOD__, '1.37' );
2422 $deleteRevisionsBatchSize = MediaWikiServices::getInstance()
2423 ->getMainConfig()->get( MainConfigNames::DeleteRevisionsBatchSize );
2424
2425 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
2426 $revCount = $this->getRevisionStore()->countRevisionsByPageId( $dbr, $this->getId() );
2427 $revCount += $safetyMargin;
2428
2429 return $revCount >= $deleteRevisionsBatchSize;
2430 }
2431
2460 public function doDeleteArticleReal(
2461 $reason, UserIdentity $deleter, $suppress = false, $u1 = null, &$error = '', $u2 = null,
2462 $tags = [], $logsubtype = 'delete', $immediate = false
2463 ) {
2464 $services = MediaWikiServices::getInstance();
2465 $deletePage = $services->getDeletePageFactory()->newDeletePage(
2466 $this,
2467 $services->getUserFactory()->newFromUserIdentity( $deleter )
2468 );
2469
2470 $status = $deletePage
2471 ->setSuppress( $suppress )
2472 ->setTags( $tags ?: [] )
2473 ->setLogSubtype( $logsubtype )
2474 ->forceImmediate( $immediate )
2475 ->keepLegacyHookErrorsSeparate()
2476 ->deleteUnsafe( $reason );
2477 $error = $deletePage->getLegacyHookErrors();
2478 if ( $status->isGood() ) {
2479 // BC with old return format
2480 if ( $deletePage->deletionsWereScheduled()[DeletePage::PAGE_BASE] ) {
2481 $status->warning( 'delete-scheduled', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
2482 } else {
2483 // @phan-suppress-next-line PhanTypeMismatchProperty Changing the type of the status parameter
2484 $status->value = $deletePage->getSuccessfulDeletionsIDs()[DeletePage::PAGE_BASE];
2485 }
2486 }
2487 return $status;
2488 }
2489
2496 public function lockAndGetLatest() {
2497 $dbw = $this->getConnectionProvider()->getPrimaryDatabase();
2498 return (int)$dbw->newSelectQueryBuilder()
2499 ->select( 'page_latest' )
2500 ->forUpdate()
2501 ->from( 'page' )
2502 ->where( [
2503 'page_id' => $this->getId(),
2504 // Typically page_id is enough, but some code might try to do
2505 // updates assuming the title is the same, so verify that
2506 'page_namespace' => $this->getTitle()->getNamespace(),
2507 'page_title' => $this->getTitle()->getDBkey()
2508 ] )
2509 ->caller( __METHOD__ )->fetchField();
2510 }
2511
2525 public static function onArticleCreate( Title $title, $maybeIsRedirect = true ) {
2526 // TODO: move this into a PageEventEmitter service
2527
2528 // Update existence markers on article/talk tabs...
2529 $other = $title->getOtherPage();
2530
2531 $services = MediaWikiServices::getInstance();
2532 $hcu = $services->getHTMLCacheUpdater();
2533 $hcu->purgeTitleUrls( [ $title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2534
2535 $title->touchLinks();
2536 $services->getRestrictionStore()->deleteCreateProtection( $title );
2537
2538 $services->getLinkCache()->invalidateTitle( $title );
2539
2540 DeferredUpdates::addCallableUpdate(
2541 static function () use ( $title, $maybeIsRedirect ) {
2542 self::queueBacklinksJobs( $title, true, $maybeIsRedirect, 'create-page' );
2543 }
2544 );
2545
2546 if ( $title->getNamespace() === NS_CATEGORY ) {
2547 // Load the Category object, which will schedule a job to create
2548 // the category table row if necessary. Checking a replica DB is ok
2549 // here, in the worst case it'll run an unnecessary recount job on
2550 // a category that probably doesn't have many members.
2551 Category::newFromTitle( $title )->getID();
2552 }
2553 }
2554
2563 public static function onArticleDelete( Title $title ) {
2564 // TODO: move this into a PageEventEmitter service
2565
2566 // Update existence markers on article/talk tabs...
2567 $other = $title->getOtherPage();
2568
2569 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2570 $hcu->purgeTitleUrls( [ $title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2571
2572 $title->touchLinks();
2573
2574 $services = MediaWikiServices::getInstance();
2575 $services->getLinkCache()->invalidateTitle( $title );
2576
2577 InfoAction::invalidateCache( $title );
2578
2579 // Invalidate caches of articles which include this page
2580 DeferredUpdates::addCallableUpdate( static function () use ( $title ) {
2581 self::queueBacklinksJobs( $title, true, true, 'delete-page' );
2582 } );
2583
2584 // TODO: Move to ChangeTrackingEventIngress when ready,
2585 // but make sure it happens on deletions and page moves by adding
2586 // the appropriate assertions to ChangeTrackingEventIngressSpyTrait.
2587 // Messages
2588 // User talk pages
2589 if ( $title->getNamespace() === NS_USER_TALK ) {
2590 $user = User::newFromName( $title->getText(), false );
2591 if ( $user ) {
2592 MediaWikiServices::getInstance()
2593 ->getTalkPageNotificationManager()
2594 ->removeUserHasNewMessages( $user );
2595 }
2596 }
2597
2598 // TODO: Create MediaEventIngress and move this there.
2599 // Image redirects
2600 $services->getRepoGroup()->getLocalRepo()->invalidateImageRedirect( $title );
2601
2602 // Purge cross-wiki cache entities referencing this page
2603 self::purgeInterwikiCheckKey( $title );
2604 }
2605
2616 public static function onArticleEdit(
2617 Title $title,
2618 ?RevisionRecord $revRecord = null,
2619 $slotsChanged = null,
2620 $maybeRedirectChanged = true
2621 ) {
2622 // TODO: move this into a PageEventEmitter service
2623
2624 DeferredUpdates::addCallableUpdate(
2625 static function () use ( $title, $slotsChanged, $maybeRedirectChanged ) {
2626 self::queueBacklinksJobs(
2627 $title,
2628 $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ),
2629 $maybeRedirectChanged,
2630 'edit-page'
2631 );
2632 }
2633 );
2634
2635 $services = MediaWikiServices::getInstance();
2636 $services->getLinkCache()->invalidateTitle( $title );
2637
2638 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2639 $hcu->purgeTitleUrls( $title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2640
2641 // Purge ?action=info cache
2642 $revid = $revRecord ? $revRecord->getId() : null;
2643 DeferredUpdates::addCallableUpdate( static function () use ( $title, $revid ) {
2644 InfoAction::invalidateCache( $title, $revid );
2645 } );
2646
2647 // Purge cross-wiki cache entities referencing this page
2648 self::purgeInterwikiCheckKey( $title );
2649 }
2650
2651 private static function queueBacklinksJobs(
2652 Title $title, bool $mainSlotChanged, bool $maybeRedirectChanged, string $causeAction
2653 ) {
2654 $services = MediaWikiServices::getInstance();
2655 $backlinkCache = $services->getBacklinkCacheFactory()->getBacklinkCache( $title );
2656
2657 $jobs = [];
2658 if ( $mainSlotChanged
2659 && $backlinkCache->hasLinks( 'templatelinks' )
2660 ) {
2661 // Invalidate caches of articles which include this page.
2662 // Only for the main slot, because only the main slot is transcluded.
2663 // TODO: MCR: not true for TemplateStyles! [SlotHandler]
2664 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2665 $title,
2666 'templatelinks',
2667 [ 'causeAction' => $causeAction ]
2668 );
2669 }
2670 // Images
2671 if ( $maybeRedirectChanged && $title->getNamespace() === NS_FILE
2672 && $backlinkCache->hasLinks( 'imagelinks' )
2673 ) {
2674 // Process imagelinks in case the redirect target has changed
2675 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2676 $title,
2677 'imagelinks',
2678 [ 'causeAction' => $causeAction ]
2679 );
2680 }
2681 // Invalidate the caches of all pages which redirect here
2682 if ( $backlinkCache->hasLinks( 'redirect' ) ) {
2683 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2684 $title,
2685 'redirect',
2686 [ 'causeAction' => $causeAction ]
2687 );
2688 }
2689 if ( $jobs ) {
2690 $services->getJobQueueGroup()->push( $jobs );
2691 }
2692 }
2693
2697 private static function purgeInterwikiCheckKey( Title $title ) {
2698 $enableScaryTranscluding = MediaWikiServices::getInstance()->getMainConfig()->get(
2699 MainConfigNames::EnableScaryTranscluding );
2700
2701 if ( !$enableScaryTranscluding ) {
2702 return; // @todo: perhaps this wiki is only used as a *source* for content?
2703 }
2704
2705 DeferredUpdates::addCallableUpdate( static function () use ( $title ) {
2706 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2707 $cache->resetCheckKey(
2708 // Do not include the namespace since there can be multiple aliases to it
2709 // due to different namespace text definitions on different wikis. This only
2710 // means that some cache invalidations happen that are not strictly needed.
2711 $cache->makeGlobalKey(
2712 'interwiki-page',
2713 WikiMap::getCurrentWikiDbDomain()->getId(),
2714 $title->getDBkey()
2715 )
2716 );
2717 } );
2718 }
2719
2726 public function getCategories() {
2727 $services = MediaWikiServices::getInstance();
2728 $id = $this->getId();
2729 if ( $id == 0 ) {
2730 return $services->getTitleFactory()->newTitleArrayFromResult( new FakeResultWrapper( [] ) );
2731 }
2732
2733 $dbr = $services->getConnectionProvider()->getReplicaDatabase( CategoryLinksTable::VIRTUAL_DOMAIN );
2734 $res = $dbr->newSelectQueryBuilder()
2735 ->select( [ 'page_title' => 'lt_title', 'page_namespace' => (string)NS_CATEGORY ] )
2736 ->from( 'categorylinks' )
2737 ->join( 'linktarget', null, [ 'cl_target_id = lt_id', 'lt_namespace = ' . NS_CATEGORY ] )
2738 ->where( [ 'cl_from' => $id ] )
2739 ->caller( __METHOD__ )
2740 ->fetchResultSet();
2741
2742 return $services->getTitleFactory()->newTitleArrayFromResult( $res );
2743 }
2744
2751 public function getHiddenCategories() {
2752 $id = $this->getId();
2753
2754 if ( $id == 0 ) {
2755 return [];
2756 }
2757
2758 $categoryLinksDb = $this->getConnectionProvider()->getReplicaDatabase( CategoryLinksTable::VIRTUAL_DOMAIN );
2759 $categoryTitles = $categoryLinksDb->newSelectQueryBuilder()
2760 ->select( 'lt_title' )
2761 ->from( 'categorylinks' )
2762 ->join( 'linktarget', null, 'cl_target_id = lt_id' )
2763 ->where( [ 'cl_from' => $id, 'lt_namespace' => NS_CATEGORY ] )
2764 ->caller( __METHOD__ )
2765 ->fetchFieldValues();
2766
2767 if ( $categoryTitles === [] ) {
2768 return [];
2769 }
2770
2771 $dbr = $this->getConnectionProvider()->getReplicaDatabase();
2772 $hiddenTitles = $dbr->newSelectQueryBuilder()
2773 ->select( [ 'page_title' ] )
2774 ->from( 'page_props' )
2775 ->join( 'page', null, 'page_id = pp_page' )
2776 ->where( [
2777 'pp_propname' => 'hiddencat',
2778 'page_namespace' => NS_CATEGORY,
2779 'page_title' => $categoryTitles
2780 ] )
2781 ->caller( __METHOD__ )
2782 ->fetchFieldValues();
2783
2784 $result = [];
2785 foreach ( $categoryTitles as $title ) {
2786 if ( in_array( $title, $hiddenTitles ) ) {
2787 $result[] = Title::makeTitle( NS_CATEGORY, $title );
2788 }
2789 }
2790
2791 return $result;
2792 }
2793
2800 public function getAutoDeleteReason() {
2801 return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle() );
2802 }
2803
2816 public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2817 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
2818 return;
2819 }
2820
2821 if ( !$this->getHookRunner()->onOpportunisticLinksUpdate( $this,
2822 $this->mTitle, $parserOutput )
2823 ) {
2824 return;
2825 }
2826
2827 $config = MediaWikiServices::getInstance()->getMainConfig();
2828
2829 $params = [
2830 'isOpportunistic' => true,
2831 'rootJobTimestamp' => $parserOutput->getCacheTime()
2832 ];
2833
2834 if ( MediaWikiServices::getInstance()->getRestrictionStore()->areRestrictionsCascading( $this->mTitle ) ) {
2835 // In general, MediaWiki does not re-run LinkUpdate (e.g. for search index, category
2836 // listings, and backlinks for Whatlinkshere), unless either the page was directly
2837 // edited, or was re-generate following a template edit propagating to an affected
2838 // page. As such, during page views when there is no valid ParserCache entry,
2839 // we re-parse and save, but leave indexes as-is.
2840 //
2841 // We make an exception for pages that have cascading protection (perhaps for a wiki's
2842 // "Main Page"). When such page is re-parsed on-demand after a parser cache miss, we
2843 // queue a high-priority LinksUpdate job, to ensure that we really protect all
2844 // content that is currently transcluded onto the page. This is important, because
2845 // wikitext supports conditional statements based on the current time, which enables
2846 // transcluding of a different subpage based on which day it is, and then show that
2847 // information on the Main Page, without the Main Page itself being edited.
2848 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush(
2849 RefreshLinksJob::newPrioritized( $this->mTitle, $params )
2850 );
2851 } elseif (
2852 (
2853 // "Dynamic" content (eg time/random magic words)
2854 !$config->get( MainConfigNames::MiserMode ) &&
2855 $parserOutput->hasReducedExpiry()
2856 )
2857 ||
2858 (
2859 // Asynchronous content
2860 $config->get( MainConfigNames::ParserCacheAsyncRefreshJobs ) &&
2861 $parserOutput->getOutputFlag( ParserOutputFlags::HAS_ASYNC_CONTENT ) &&
2862 !$parserOutput->getOutputFlag( ParserOutputFlags::ASYNC_NOT_READY )
2863 )
2864 ) {
2865 // Assume the output contains "dynamic" time/random based magic words
2866 // or asynchronous content that wasn't "ready" the first time the
2867 // page was parsed.
2868 // Only update pages that expired due to dynamic content and NOT due to edits
2869 // to referenced templates/files. When the cache expires due to dynamic content,
2870 // page_touched is unchanged. We want to avoid triggering redundant jobs due to
2871 // views of pages that were just purged via HTMLCacheUpdateJob. In that case, the
2872 // template/file edit already triggered recursive RefreshLinksJob jobs.
2873 if ( $this->getLinksTimestamp() > $this->getTouched() ) {
2874 // If a page is uncacheable, do not keep spamming a job for it.
2875 // Although it would be de-duplicated, it would still waste I/O.
2876 $services = MediaWikiServices::getInstance()->getObjectCacheFactory();
2877 $cache = $services->getLocalClusterInstance();
2878 $key = $cache->makeKey( 'dynamic-linksupdate', 'last', $this->getId() );
2879 $ttl = max( $parserOutput->getCacheExpiry(), 3600 );
2880 if ( $cache->add( $key, time(), $ttl ) ) {
2881 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush(
2882 RefreshLinksJob::newDynamic( $this->mTitle, $params )
2883 );
2884 }
2885 }
2886 }
2887 }
2888
2896 public function isLocal() {
2897 return true;
2898 }
2899
2909 public function getWikiDisplayName() {
2910 $sitename = MediaWikiServices::getInstance()->getMainConfig()->get(
2911 MainConfigNames::Sitename );
2912 return $sitename;
2913 }
2914
2923 public function getSourceURL() {
2924 return $this->getTitle()->getCanonicalURL();
2925 }
2926
2933 public function __wakeup() {
2934 // Make sure we re-fetch the latest state from the database.
2935 // In particular, the latest revision may have changed.
2936 // As a side-effect, this makes sure mLastRevision doesn't
2937 // end up being an instance of the old Revision class (see T259181),
2938 // especially since that class was removed entirely in 1.37.
2939 $this->clear();
2940 }
2941
2946 public function getNamespace(): int {
2947 return $this->getTitle()->getNamespace();
2948 }
2949
2954 public function getDBkey(): string {
2955 return $this->getTitle()->getDBkey();
2956 }
2957
2962 public function getWikiId() {
2963 return $this->getTitle()->getWikiId();
2964 }
2965
2970 public function canExist(): bool {
2971 return true;
2972 }
2973
2978 public function __toString(): string {
2979 return $this->mTitle->__toString();
2980 }
2981
2986 public function isSamePageAs( PageReference $other ): bool {
2987 // NOTE: keep in sync with PageReferenceValue::isSamePageAs()!
2988 return $this->getWikiId() === $other->getWikiId()
2989 && $this->getNamespace() === $other->getNamespace()
2990 && $this->getDBkey() === $other->getDBkey();
2991 }
2992
3005 // TODO: replace individual member fields with a PageRecord instance that is always present
3006
3007 if ( !$this->mDataLoaded ) {
3008 $this->loadPageData();
3009 }
3010
3011 Assert::precondition(
3012 $this->exists(),
3013 'This WikiPage instance does not represent an existing page: ' . $this->mTitle
3014 );
3015
3016 return new PageStoreRecord(
3017 (object)[
3018 'page_id' => $this->getId(),
3019 'page_namespace' => $this->mTitle->getNamespace(),
3020 'page_title' => $this->mTitle->getDBkey(),
3021 'page_latest' => $this->mLatest,
3022 'page_is_new' => $this->mIsNew ? 1 : 0,
3023 'page_is_redirect' => $this->mPageIsRedirectField ? 1 : 0,
3024 'page_touched' => $this->getTouched(),
3025 'page_lang' => $this->getLanguage()
3026 ],
3027 PageIdentity::LOCAL
3028 );
3029 }
3030
3034 private function getConnectionProvider(): \Wikimedia\Rdbms\IConnectionProvider {
3035 return MediaWikiServices::getInstance()->getConnectionProvider();
3036 }
3037
3038}
3039
3041class_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,...
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
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:71
Displays information about a page.
Category objects are immutable, strictly speaking.
Definition Category.php:30
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:83
int $mDataLoadedFrom
One of the READ_* constants.
Definition WikiPage.php:133
getMinorEdit()
Returns true if last revision was marked as "minor edit".
Definition WikiPage.php:891
getUser( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Definition WikiPage.php:815
loadLastEdit()
Loads everything except the text This isn't necessary for all uses, so it's only done if needed.
Definition WikiPage.php:708
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:490
getContentHandler()
Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
Definition WikiPage.php:242
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 latest revision.
Definition WikiPage.php:776
clear()
Clear the object.
Definition WikiPage.php:259
__construct(PageIdentity $pageIdentity)
Definition WikiPage.php:165
getRevisionRecord()
Get the latest revision.
Definition WikiPage.php:758
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:117
string $mTimestamp
Timestamp of the latest revision or empty string if not loaded.
Definition WikiPage.php:143
getLatest( $wikiId=self::LOCAL)
Get the page_latest field.
Definition WikiPage.php:695
getRedirectURL( $rt)
Get the Title object or URL to use for a redirect.
isRedirect()
Is the page a redirect, according to secondary tracking tables? If this is true, getRedirectTarget() ...
Definition WikiPage.php:578
getAutoDeleteReason()
Auto-generates a deletion reason.
pageDataFromId( $dbr, $id, $options=[])
Fetch a page record matching the requested ID.
Definition WikiPage.php:395
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:344
clearPreparedEdit()
Clear the mPreparedEdit cache field, as may be needed by mutable content types.
Definition WikiPage.php:291
getTitle()
Get the title object of the article.
Definition WikiPage.php:251
getRedirectTarget()
If this page is a redirect, get its target.
Definition WikiPage.php:978
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.
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:181
isNew()
Tests if the page is new (only has one revision).
Definition WikiPage.php:596
followRedirect()
Get the Title object or URL this page redirects to.
static convertSelectType( $type)
Convert deprecated 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants.
Definition WikiPage.php:191
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:836
clearCacheFields()
Clear the object cache fields.
Definition WikiPage.php:270
insertRedirectEntry(LinkTarget $rt, $oldLatest=null)
Insert or update the redirect table entry for this page to indicate it redirects to $rt.
Definition WikiPage.php:992
doViewUpdates(Authority $performer, $oldRev=null, $oldRevDeprecated=null)
Do standard deferred updates after page view (existing or missing page)
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:661
checkTouched()
Loads page_touched and returns a value indicating if it should be used.
Definition WikiPage.php:653
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:541
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:371
loadPageData( $from=IDBAccessObject::READ_NORMAL)
Load the object from a given source by title.
Definition WikiPage.php:413
__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:614
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:123
getParserOutput(?ParserOptions $parserOptions=null, $oldid=null, $noCache=false, array $options=[], array &$errors=[])
Get a ParserOutput for the given ParserOptions and revision ID.
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:855
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:916
wasLoadedFrom( $from)
Checks whether the page data was loaded using the given database access mode (or better).
Definition WikiPage.php:462
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.
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:876
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new page object.
Definition WikiPage.php:308
setTimestamp( $ts)
Set the page timestamp (use only to avoid DB queries)
Definition WikiPage.php:801
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:683
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:568
Set options of the Parser.
ParserOutput is a rendering of a Content object or a message.
getOutputFlag(ParserOutputFlags|string $flag)
Provides a uniform interface to various boolean flags stored in the ParserOutput.
hasReducedExpiry()
Check whether the cache TTL was lowered from the site default.
getCacheExpiry()
Returns the number of seconds after which this object should expire.This method is used by ParserCach...
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(int $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:69
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:1708
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition Title.php:3296
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1034
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:1007
Class to walk into a list of User objects.
Definition UserArray.php:19
User class for the MediaWiki software.
Definition User.php:129
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, 'EnableChunkedUploads'=> 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'=> '', 'SharedUploadDBschema'=> null, 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'DefaultLockManager'=> null, 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> true, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -l $lang -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'MaxAnimatedWebPArea'=> 12500000, 'WebPThumbnailType'=>['webp', 'image/webp',], 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'RemoteVirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'SplitParsoidParserCache'=> false, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'EnableSectionShare'=> false, 'PageShareSkinsEnabled'=>[], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'RestrictUserPageEditing' => false, 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editmywatchlist' => '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', 'managesessions' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => [ 'class' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'needsPage' => false, ], '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', 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestLocalModuleTestBaseUrl' => null, 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'RestTermsOfServiceUrl' => null, '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, 'HTTPUserAgentContact' => false, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => 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' => [ ], 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], 'UseParsoidLinksUpdate' => true, 'UseParsoidMessages' => true, 'UseParsoidParser' => false, 'SiteLookup' => [ ], ], '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', 'SharedUploadDBschema' => [ 'string', 'null', ], 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'DefaultLockManager' => [ 'string', 'null', ], 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'WebPThumbnailType' => 'array', 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'RemoteVirtualDomainsMapping' => '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', 'SplitParsoidParserCache' => 'boolean', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'PageShareSkinsEnabled' => 'array', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'RestrictUserPageEditing' => 'boolean', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'ReauthenticateForActions' => 'object', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RestrictedTagViewRights' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestLocalModuleTestBaseUrl' => [ 'string', 'null', ], 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'RestTermsOfServiceUrl' => [ 'string', 'null', ], 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'HTTPUserAgentContact' => [ 'string', 'boolean', ], 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', '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', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', 'UseParsoidLinksUpdate' => [ 'boolean', 'null', ], 'UseParsoidMessages' => [ 'boolean', 'null', ], 'UseParsoidParser' => 'boolean', 'SiteLookup' => 'object', ], 'mergeStrategy' => [ 'WebPThumbnailType' => 'replace', 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_recursive', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => [ 'integer', 'array', ], ], ], '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', ], 'skipRedirects' => [ 'type' => 'bool', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'availability' => [ 'type' => 'string', ], 'groups' => [ 'type' => 'array', ], ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
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
ParserOutputFlags
Registry of flags used with ParserOutput::{get,set}OutputFlag() within MediaWiki core.