MediaWiki master
WikiPage.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Page;
8
9use BadMethodCallException;
10use InvalidArgumentException;
17use MediaWiki\DAO\WikiAwareEntityTrait;
22use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
56use RuntimeException;
57use stdClass;
58use Stringable;
59use Wikimedia\Assert\Assert;
60use Wikimedia\Assert\PreconditionException;
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
977 public function getRedirectTarget() {
978 $target = MediaWikiServices::getInstance()->getRedirectLookup()->getRedirectTarget( $this );
979 return Title::castFromLinkTarget( $target );
980 }
981
989 public function insertRedirectEntry( LinkTarget $rt, $oldLatest = null ) {
990 return MediaWikiServices::getInstance()->getRedirectStore()
991 ->updateRedirectTarget( $this, $rt );
992 }
993
999 public function followRedirect() {
1000 return $this->getRedirectURL( $this->getRedirectTarget() );
1001 }
1002
1010 public function getRedirectURL( $rt ) {
1011 if ( !$rt ) {
1012 return false;
1013 }
1014
1015 if ( $rt->isExternal() ) {
1016 if ( $rt->isLocal() ) {
1017 // Offsite wikis need an HTTP redirect.
1018 // This can be hard to reverse and may produce loops,
1019 // so they may be disabled in the site configuration.
1020 $source = $this->mTitle->getFullURL( 'redirect=no' );
1021 return $rt->getFullURL( [ 'rdfrom' => $source ] );
1022 } else {
1023 // External pages without "local" bit set are not valid
1024 // redirect targets
1025 return false;
1026 }
1027 }
1028
1029 if ( $rt->isSpecialPage() ) {
1030 // Gotta handle redirects to special pages differently:
1031 // Fill the HTTP response "Location" header and ignore the rest of the page we're on.
1032 // Some pages are not valid targets.
1033 if ( $rt->isValidRedirectTarget() ) {
1034 return $rt->getFullURL();
1035 } else {
1036 return false;
1037 }
1038 } elseif ( !$rt->isValidRedirectTarget() ) {
1039 // We somehow got a bad redirect target into the database (T278367)
1040 return false;
1041 }
1042
1043 return $rt;
1044 }
1045
1051 public function getContributors() {
1052 // @todo: This is expensive; cache this info somewhere.
1053
1054 $services = MediaWikiServices::getInstance();
1055 $dbr = $services->getConnectionProvider()->getReplicaDatabase();
1056 $actorNormalization = $services->getActorNormalization();
1057 $userIdentityLookup = $services->getUserIdentityLookup();
1058
1059 $user = $this->getUser()
1060 ? User::newFromId( $this->getUser() )
1061 : User::newFromName( $this->getUserText(), false );
1062
1063 $res = $dbr->newSelectQueryBuilder()
1064 ->select( [
1065 'user_id' => 'actor_user',
1066 'user_name' => 'actor_name',
1067 'actor_id' => 'MIN(rev_actor)',
1068 'user_real_name' => 'MIN(user_real_name)',
1069 'timestamp' => 'MAX(rev_timestamp)',
1070 ] )
1071 ->from( 'revision' )
1072 ->join( 'actor', null, 'rev_actor = actor_id' )
1073 ->leftJoin( 'user', null, 'actor_user = user_id' )
1074 ->where( [
1075 'rev_page' => $this->getId(),
1076 // The user who made the top revision gets credited as "this page was last edited by
1077 // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
1078 $dbr->expr( 'rev_actor', '!=', $actorNormalization->findActorId( $user, $dbr ) ),
1079 // Username hidden?
1080 $dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0',
1081 ] )
1082 ->groupBy( [ 'actor_user', 'actor_name' ] )
1083 ->orderBy( 'timestamp', SelectQueryBuilder::SORT_DESC )
1084 ->caller( __METHOD__ )
1085 ->fetchResultSet();
1086 return new UserArrayFromResult( $res );
1087 }
1088
1096 public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
1097 // NOTE: Keep in sync with ParserOutputAccess::shouldUseCache().
1098 // TODO: Once ParserOutputAccess is stable, deprecated this method.
1099 return $this->exists()
1100 && ( $oldId === null || $oldId === 0 || $oldId === $this->getLatest() )
1101 && $this->getContentHandler()->isParserCacheSupported();
1102 }
1103
1123 public function getParserOutput(
1124 ?ParserOptions $parserOptions = null, $oldid = null, $noCache = false,
1125 array $options = [], array &$errors = []
1126 ) {
1127 if ( $oldid instanceof RevisionRecord ) {
1128 $revision = $oldid;
1129 } elseif ( $oldid ) {
1130 $revision = $this->getRevisionStore()->getRevisionByTitle( $this->getTitle(), $oldid );
1131
1132 if ( !$revision ) {
1133 return false;
1134 }
1135 } else {
1136 $revision = $this->getRevisionRecord();
1137 }
1138
1139 if ( !$parserOptions ) {
1140 $parserOptions = ParserOptions::newFromAnon();
1141 }
1142
1143 if ( $noCache ) {
1144 $options += [ ParserOutputAccess::OPT_NO_CACHE => true ];
1145 }
1146
1147 $status = MediaWikiServices::getInstance()->getParserOutputAccess()->getParserOutput(
1148 $this, $parserOptions, $revision, $options
1149 );
1150 if ( $status->isOK() ) {
1151 return $status->getValue();
1152 } else {
1153 $errors = [ ...$status->getMessages() ];
1154 // convert null to false
1155 return false;
1156 }
1157 }
1158
1166 public function doViewUpdates(
1167 Authority $performer,
1168 $oldRev = null,
1169 $oldRevDeprecated = null
1170 ) {
1171 if ( func_num_args() > 2 ) {
1172 wfDeprecatedMsg( 'Passing $oldid to ' . __METHOD__ . ' is deprecated since 1.46.' );
1173 $oldRev = $oldRevDeprecated;
1174 }
1175
1176 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
1177 return;
1178 }
1179
1180 DeferredUpdates::addCallableUpdate(
1181 function () use ( $performer ) {
1182 // In practice, these hook handlers simply debounce into a post-send
1183 // to do their work since none of the use cases for this hook require
1184 // a blocking pre-send callback.
1185 //
1186 // TODO: Move this hook to post-send.
1187 //
1188 // For now, it is unofficially possible for an extension to use
1189 // onPageViewUpdates to try to insert JavaScript via global $wgOut.
1190 // This isn't supported (the hook doesn't pass OutputPage), and
1191 // can't be since OutputPage may be disabled or replaced on some
1192 // pages that we do support page view updates for. We also run
1193 // this hook after HTMLFileCache, which also naturally can't
1194 // support modifying OutputPage. Handlers that modify the page
1195 // may use onBeforePageDisplay instead, which runs behind
1196 // HTMLFileCache and won't run on non-OutputPage responses.
1197 $legacyUser = MediaWikiServices::getInstance()
1198 ->getUserFactory()
1199 ->newFromAuthority( $performer );
1200 $this->getHookRunner()->onPageViewUpdates( $this, $legacyUser );
1201 },
1202 DeferredUpdates::PRESEND
1203 );
1204
1205 // Update newtalk and watchlist notification status
1206 MediaWikiServices::getInstance()
1207 ->getWatchlistManager()
1208 ->clearTitleUserNotifications( $performer, $this, $oldRev );
1209 }
1210
1217 public function doPurge() {
1218 if ( !$this->getHookRunner()->onArticlePurge( $this ) ) {
1219 return false;
1220 }
1221
1222 $this->mTitle->invalidateCache();
1223
1224 // Clear file cache and send purge after above page_touched update was committed
1225 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
1226 $hcu->purgeTitleUrls( $this->mTitle, $hcu::PURGE_PRESEND );
1227
1228 if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI ) {
1229 MediaWikiServices::getInstance()->getMessageCache()
1230 ->updateMessageOverride( $this->mTitle, $this->getContent() );
1231 }
1232 InfoAction::invalidateCache( $this->mTitle, $this->getLatest() );
1233
1234 return true;
1235 }
1236
1255 public function insertOn( $dbw, $pageId = null ) {
1256 $pageIdForInsert = $pageId ? [ 'page_id' => $pageId ] : [];
1257 $row = [
1258 'page_namespace' => $this->mTitle->getNamespace(),
1259 'page_title' => $this->mTitle->getDBkey(),
1260 'page_is_redirect' => 0, // Will set this shortly...
1261 'page_is_new' => 1,
1262 'page_random' => wfRandom(),
1263 'page_touched' => $dbw->timestamp(),
1264 'page_latest' => 0, // Fill this in shortly...
1265 'page_len' => 0, // Fill this in shortly...
1266 ] + $pageIdForInsert;
1267 $dbw->newInsertQueryBuilder()
1268 ->insertInto( 'page' )
1269 ->ignore()
1270 ->row( $row )
1271 ->caller( __METHOD__ )->execute();
1272
1273 if ( $dbw->affectedRows() > 0 ) {
1274 $newid = $pageId ? (int)$pageId : $dbw->insertId();
1275 $this->mId = $newid;
1276 $this->mTitle->resetArticleID( $newid );
1277
1278 // Duplicate the row on secondary links storage if needed but set the page_id
1279 $row['page_id'] = $newid;
1280 $insert = $dbw->newInsertQueryBuilder()
1281 ->insertInto( 'page' )
1282 ->ignore()
1283 ->row( $row )
1284 ->caller( __METHOD__ );
1285 MediaWikiServices::getInstance()->getLinkWriteDuplicator()->duplicate( $insert );
1286
1287 return $newid;
1288 } else {
1289 return false; // nothing changed
1290 }
1291 }
1292
1310 public function updateRevisionOn(
1311 $dbw,
1312 RevisionRecord $revision,
1313 $lastRevision = null,
1314 $lastRevIsRedirect = null
1315 ) {
1316 // TODO: move into PageUpdater or PageStore
1317 // NOTE: when doing that, make sure cached fields get reset in doUserEditContent,
1318 // and in the compat stub!
1319
1320 $revId = $revision->getId();
1321 Assert::parameter( $revId > 0, '$revision->getId()', 'must be > 0' );
1322
1323 $content = $revision->getContent( SlotRecord::MAIN );
1324 $len = $content ? $content->getSize() : 0;
1325 $rt = $content ? $content->getRedirectTarget() : null;
1326 $isNew = $lastRevision === 0;
1327 $isRedirect = $rt !== null;
1328
1329 $conditions = [ 'page_id' => $this->getId() ];
1330
1331 if ( $lastRevision !== null ) {
1332 // An extra check against threads stepping on each other
1333 $conditions['page_latest'] = $lastRevision;
1334 }
1335
1336 $model = $revision->getMainContentModel();
1337
1338 $row = [ /* SET */
1339 'page_latest' => $revId,
1340 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
1341 'page_is_new' => $isNew ? 1 : 0,
1342 'page_is_redirect' => $isRedirect ? 1 : 0,
1343 'page_len' => $len,
1344 'page_content_model' => $model,
1345 ];
1346
1347 $update = $dbw->newUpdateQueryBuilder()
1348 ->update( 'page' )
1349 ->set( $row )
1350 ->where( $conditions )
1351 ->caller( __METHOD__ );
1352 $update->execute();
1353 MediaWikiServices::getInstance()->getLinkWriteDuplicator()->duplicate( $update );
1354
1355 $result = $dbw->affectedRows() > 0;
1356 if ( $result ) {
1357 $insertedRow = $this->pageData( $dbw, [ 'page_id' => $this->getId() ] );
1358
1359 if ( !$insertedRow ) {
1360 throw new RuntimeException( 'Failed to load freshly inserted row' );
1361 }
1362
1363 $this->mTitle->loadFromRow( $insertedRow );
1364 MediaWikiServices::getInstance()->getRedirectStore()
1365 ->updateRedirectTarget( $this, $rt, $lastRevIsRedirect );
1366 $this->setLastEdit( $revision );
1367 $this->mPageIsRedirectField = (bool)$rt;
1368 $this->mIsNew = $isNew;
1369
1370 // Update the LinkCache.
1371 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1372 $linkCache->addGoodLinkObjFromRow(
1373 $this->mTitle,
1374 $insertedRow
1375 );
1376 }
1377
1378 return $result;
1379 }
1380
1394 $aSlots = $a->getSlots();
1395 $bSlots = $b->getSlots();
1396 $changedRoles = $aSlots->getRolesWithDifferentContent( $bSlots );
1397
1398 return ( $changedRoles !== [ SlotRecord::MAIN ] && $changedRoles !== [] );
1399 }
1400
1411 public function supportsSections() {
1412 return $this->getContentHandler()->supportsSections();
1413 }
1414
1428 public function replaceSectionContent(
1429 $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
1430 ) {
1431 $baseRevId = null;
1432 if ( $edittime && $sectionId !== 'new' ) {
1433 $lb = $this->getDBLoadBalancer();
1434 $rev = $this->getRevisionStore()->getRevisionByTimestamp( $this->mTitle, $edittime );
1435 // Try the primary database if this thread may have just added it.
1436 // The logic to fallback to the primary database if the replica is missing
1437 // the revision could be generalized into RevisionStore, but we don't want
1438 // to encourage loading of revisions by timestamp.
1439 if ( !$rev
1440 && $lb->hasReplicaServers()
1441 && $lb->hasOrMadeRecentPrimaryChanges()
1442 ) {
1443 $rev = $this->getRevisionStore()->getRevisionByTimestamp(
1444 $this->mTitle, $edittime, IDBAccessObject::READ_LATEST );
1445 }
1446 if ( $rev ) {
1447 $baseRevId = $rev->getId();
1448 }
1449 }
1450
1451 return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
1452 }
1453
1466 public function replaceSectionAtRev( $sectionId, Content $sectionContent,
1467 $sectionTitle = '', $baseRevId = null
1468 ) {
1469 if ( strval( $sectionId ) === '' ) {
1470 // Whole-page edit; let the whole text through
1471 $newContent = $sectionContent;
1472 } else {
1473 if ( !$this->supportsSections() ) {
1474 throw new BadMethodCallException( "sections not supported for content model " .
1475 $this->getContentHandler()->getModelID() );
1476 }
1477
1478 // T32711: always use current version when adding a new section
1479 if ( $baseRevId === null || $sectionId === 'new' ) {
1480 $oldContent = $this->getContent();
1481 } else {
1482 $revRecord = $this->getRevisionStore()->getRevisionById( $baseRevId );
1483 if ( !$revRecord ) {
1484 wfDebug( __METHOD__ . " asked for bogus section (page: " .
1485 $this->getId() . "; section: $sectionId)" );
1486 return null;
1487 }
1488
1489 $oldContent = $revRecord->getContent( SlotRecord::MAIN );
1490 }
1491
1492 if ( !$oldContent ) {
1493 wfDebug( __METHOD__ . ": no page text" );
1494 return null;
1495 }
1496
1497 $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
1498 }
1499
1500 return $newContent;
1501 }
1502
1512 public function checkFlags( $flags ) {
1513 if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
1514 if ( $this->exists() ) {
1515 $flags |= EDIT_UPDATE;
1516 } else {
1517 $flags |= EDIT_NEW;
1518 }
1519 }
1520
1521 return $flags;
1522 }
1523
1551 private function getDerivedDataUpdater(
1552 ?UserIdentity $forUser = null,
1553 ?RevisionRecord $forRevision = null,
1554 ?RevisionSlotsUpdate $forUpdate = null,
1555 $forEdit = false
1556 ) {
1557 if ( !$forRevision && !$forUpdate ) {
1558 // NOTE: can't re-use an existing derivedDataUpdater if we don't know what the caller is
1559 // going to use it with.
1560 $this->derivedDataUpdater = null;
1561 }
1562
1563 if ( $this->derivedDataUpdater && !$this->derivedDataUpdater->isContentPrepared() ) {
1564 // NOTE: can't re-use an existing derivedDataUpdater if other code that has a reference
1565 // to it did not yet initialize it, because we don't know what data it will be
1566 // initialized with.
1567 $this->derivedDataUpdater = null;
1568 }
1569
1570 // XXX: It would be nice to have an LRU cache instead of trying to re-use a single instance.
1571 // However, there is no good way to construct a cache key. We'd need to check against all
1572 // cached instances.
1573
1574 if ( $this->derivedDataUpdater
1575 && !$this->derivedDataUpdater->isReusableFor(
1576 $forUser,
1577 $forRevision,
1578 $forUpdate,
1579 $forEdit ? $this->getLatest() : null
1580 )
1581 ) {
1582 $this->derivedDataUpdater = null;
1583 }
1584
1585 if ( !$this->derivedDataUpdater ) {
1586 $this->derivedDataUpdater =
1587 $this->getPageUpdaterFactory()->newDerivedPageDataUpdater( $this );
1588 }
1589
1590 return $this->derivedDataUpdater;
1591 }
1592
1640 public function doUserEditContent(
1641 Content $content,
1642 Authority $performer,
1643 $summary,
1644 $flags = 0,
1645 $originalRevId = false,
1646 $tags = [],
1647 $undidRevId = 0
1648 ): PageUpdateStatus {
1649 $useNPPatrol = MediaWikiServices::getInstance()->getMainConfig()->get(
1650 MainConfigNames::UseNPPatrol );
1651 $useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get(
1652 MainConfigNames::UseRCPatrol );
1653 if ( !( $summary instanceof CommentStoreComment ) ) {
1654 $summary = CommentStoreComment::newUnsavedComment( trim( $summary ) );
1655 }
1656
1657 // TODO: this check is here for backwards-compatibility with 1.31 behavior.
1658 // Checking the minoredit right should be done in the same place the 'bot' right is
1659 // checked for the EDIT_FORCE_BOT flag, which is currently in EditPage::attemptSave.
1660 if ( ( $flags & EDIT_MINOR ) && !$performer->isAllowed( 'minoredit' ) ) {
1661 $flags &= ~EDIT_MINOR;
1662 }
1663
1664 $slotsUpdate = new RevisionSlotsUpdate();
1665 $slotsUpdate->modifyContent( SlotRecord::MAIN, $content );
1666
1667 // NOTE: while doUserEditContent() executes, callbacks to getDerivedDataUpdater and
1668 // prepareContentForEdit will generally use the DerivedPageDataUpdater that is also
1669 // used by this PageUpdater. However, there is no guarantee for this.
1670 $updater = $this->newPageUpdater( $performer, $slotsUpdate )
1671 ->setContent( SlotRecord::MAIN, $content )
1672 ->setOriginalRevisionId( $originalRevId );
1673 if ( $undidRevId ) {
1674 $updater->setCause( PageUpdateCauses::CAUSE_UNDO );
1675 $updater->markAsRevert(
1676 EditResult::REVERT_UNDO,
1677 $undidRevId,
1678 $originalRevId ?: null
1679 );
1680 }
1681
1682 $needsPatrol = $useRCPatrol || ( $useNPPatrol && !$this->exists() );
1683
1684 // TODO: this logic should not be in the storage layer, it's here for compatibility
1685 // with 1.31 behavior. Applying the 'autopatrol' right should be done in the same
1686 // place the 'bot' right is handled, which is currently in EditPage::attemptSave.
1687
1688 if ( $needsPatrol && $performer->authorizeWrite( 'autopatrol', $this->getTitle() ) ) {
1689 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
1690 }
1691
1692 $updater->addTags( $tags );
1693
1694 $revRec = $updater->saveRevision(
1695 $summary,
1696 $flags
1697 );
1698
1699 // $revRec will be null if the edit failed, or if no new revision was created because
1700 // the content did not change.
1701 if ( $revRec ) {
1702 // update cached fields
1703 // TODO: this is currently redundant to what is done in updateRevisionOn.
1704 // But updateRevisionOn() should move into PageStore, and then this will be needed.
1705 $this->setLastEdit( $revRec );
1706 }
1707
1708 return $updater->getStatus();
1709 }
1710
1731 public function newPageUpdater( $performer, ?RevisionSlotsUpdate $forUpdate = null ) {
1732 if ( $performer instanceof Authority ) {
1733 // TODO: Deprecate this. But better get rid of this method entirely.
1734 $performer = $performer->getUser();
1735 }
1736
1737 $pageUpdater = $this->getPageUpdaterFactory()->newPageUpdaterForDerivedPageDataUpdater(
1738 $this,
1739 $performer,
1740 $this->getDerivedDataUpdater( $performer, null, $forUpdate, true )
1741 );
1742
1743 return $pageUpdater;
1744 }
1745
1760 public function makeParserOptions( $context ) {
1761 return self::makeParserOptionsFromTitleAndModel(
1762 $this->getTitle(), $this->getContentModel(), $context
1763 );
1764 }
1765
1775 PageReference $pageRef, string $contentModel, $context
1776 ) {
1777 $options = ParserOptions::newCanonical( $context );
1778
1779 $title = Title::newFromPageReference( $pageRef );
1780 if ( $title->isConversionTable() ) {
1781 // @todo ConversionTable should become a separate content model, so
1782 // we don't need special cases like this one, but see T313455.
1783 $options->disableContentConversion();
1784 }
1785 # Add in the preferred variant from the URL or user preferences
1786 $services = MediaWikiServices::getInstance();
1787 $languageConverterFactory = $services->getLanguageConverterFactory();
1788 if ( !$languageConverterFactory->isConversionDisabled() ) {
1789 $converter = $languageConverterFactory->getLanguageConverter(
1790 $title->getPageLanguage()
1791 );
1792 if ( $converter->hasVariants() ) {
1793 $variant = $services->getLanguageFactory()->getLanguage(
1794 $converter->getPreferredVariant()
1795 );
1796 $options->setVariant( $variant );
1797 }
1798 }
1799
1800 return $options;
1801 }
1802
1823 public function prepareContentForEdit(
1824 Content $content,
1825 ?RevisionRecord $revision,
1826 UserIdentity $user,
1827 $serialFormat = null,
1828 $useStash = true
1829 ) {
1830 $slots = RevisionSlotsUpdate::newFromContent( [ SlotRecord::MAIN => $content ] );
1831 $updater = $this->getDerivedDataUpdater( $user, $revision, $slots );
1832
1833 if ( !$updater->isUpdatePrepared() ) {
1834 $updater->prepareContent( $user, $slots, $useStash );
1835
1836 if ( $revision ) {
1837 $updater->prepareUpdate(
1838 $revision,
1839 [
1840 'causeAction' => 'prepare-edit',
1841 'causeAgent' => $user->getName(),
1842 ]
1843 );
1844 }
1845 }
1846
1847 return $updater->getPreparedEdit();
1848 }
1849
1865 public function doEditUpdates(
1866 RevisionRecord $revisionRecord,
1867 UserIdentity $user,
1868 array $options = []
1869 ) {
1870 wfDeprecated( __METHOD__, '1.32' ); // emitting warnings since 1.44
1871
1872 $options += [
1873 'causeAction' => 'edit-page',
1874 'causeAgent' => $user->getName(),
1875 'emitEvents' => false // prior page state is unknown, can't emit events
1876 ];
1877
1878 $updater = $this->getDerivedDataUpdater( $user, $revisionRecord );
1879
1880 $updater->prepareUpdate( $revisionRecord, $options );
1881
1882 $updater->doUpdates();
1883 }
1884
1897 public function updateParserCache( array $options = [] ) {
1898 $revision = $this->getRevisionRecord();
1899 if ( !$revision || !$revision->getId() ) {
1900 LoggerFactory::getInstance( 'wikipage' )->info(
1901 __METHOD__ . ' called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
1902 );
1903 return;
1904 }
1905 $userIdentity = $revision->getUser( RevisionRecord::RAW );
1906
1907 $updater = $this->getDerivedDataUpdater( $userIdentity, $revision );
1908 $updater->prepareUpdate( $revision, $options );
1909 $updater->doParserCacheUpdate();
1910 }
1911
1940 public function doSecondaryDataUpdates( array $options = [] ) {
1941 $options['recursive'] ??= true;
1942 $revision = $this->getRevisionRecord();
1943 if ( !$revision || !$revision->getId() ) {
1944 LoggerFactory::getInstance( 'wikipage' )->info(
1945 __METHOD__ . ' called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
1946 );
1947 return;
1948 }
1949 $userIdentity = $revision->getUser( RevisionRecord::RAW );
1950
1951 $updater = $this->getDerivedDataUpdater( $userIdentity, $revision );
1952 $updater->prepareUpdate( $revision, $options );
1953 $updater->doSecondaryDataUpdates( $options );
1954 }
1955
1970 public function doUpdateRestrictions( array $limit, array $expiry,
1971 &$cascade, $reason, UserIdentity $user, $tags = []
1972 ) {
1973 $services = MediaWikiServices::getInstance();
1974 $readOnlyMode = $services->getReadOnlyMode();
1975 if ( $readOnlyMode->isReadOnly() ) {
1976 return Status::newFatal( 'readonlytext', $readOnlyMode->getReason() );
1977 }
1978
1979 $this->loadPageData( IDBAccessObject::READ_LATEST );
1980 $restrictionStore = $services->getRestrictionStore();
1981 $restrictionStore->loadRestrictions( $this->mTitle, IDBAccessObject::READ_LATEST );
1982 $restrictionTypes = $restrictionStore->listApplicableRestrictionTypes( $this->mTitle );
1983 $id = $this->getId();
1984
1985 if ( !$cascade ) {
1986 $cascade = false;
1987 }
1988
1989 // Take this opportunity to purge out expired restrictions
1990 Title::purgeExpiredRestrictions();
1991
1992 // @todo: Same limitations as described in ProtectionForm.php (line 37);
1993 // we expect a single selection, but the schema allows otherwise.
1994 $isProtected = false;
1995 $protect = false;
1996 $changed = false;
1997
1998 $dbw = $services->getConnectionProvider()->getPrimaryDatabase();
1999 $restrictionMapBefore = [];
2000 $restrictionMapAfter = [];
2001
2002 foreach ( $restrictionTypes as $action ) {
2003 if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) {
2004 $expiry[$action] = 'infinity';
2005 }
2006
2007 // Get current restrictions on $action
2008 $restrictionMapBefore[$action] = $restrictionStore->getRestrictions( $this->mTitle, $action );
2009 $limit[$action] ??= '';
2010
2011 if ( $limit[$action] === '' ) {
2012 $restrictionMapAfter[$action] = [];
2013 } else {
2014 $protect = true;
2015 $restrictionMapAfter[$action] = explode( ',', $limit[$action] );
2016 }
2017
2018 $current = implode( ',', $restrictionMapBefore[$action] );
2019 if ( $current != '' ) {
2020 $isProtected = true;
2021 }
2022
2023 if ( $limit[$action] != $current ) {
2024 $changed = true;
2025 } elseif ( $limit[$action] != '' ) {
2026 // Only check expiry change if the action is actually being
2027 // protected, since expiry does nothing on an not-protected
2028 // action.
2029 if ( $restrictionStore->getRestrictionExpiry( $this->mTitle, $action ) != $expiry[$action] ) {
2030 $changed = true;
2031 }
2032 }
2033 }
2034
2035 if ( !$changed && $protect && $restrictionStore->areRestrictionsCascading( $this->mTitle ) != $cascade ) {
2036 $changed = true;
2037 }
2038
2039 // If nothing has changed, do nothing
2040 if ( !$changed ) {
2041 return Status::newGood();
2042 }
2043
2044 if ( !$protect ) { // No protection at all means unprotection
2045 $revCommentMsg = 'unprotectedarticle-comment';
2046 $logAction = 'unprotect';
2047 } elseif ( $isProtected ) {
2048 $revCommentMsg = 'modifiedarticleprotection-comment';
2049 $logAction = 'modify';
2050 } else {
2051 $revCommentMsg = 'protectedarticle-comment';
2052 $logAction = 'protect';
2053 }
2054
2055 $logRelationsValues = [];
2056 $logRelationsField = null;
2057 $logParamsDetails = [];
2058
2059 // Null revision (used for change tag insertion)
2060 $dummyRevisionRecord = null;
2061
2062 $legacyUser = $services->getUserFactory()->newFromUserIdentity( $user );
2063 if ( !$this->getHookRunner()->onArticleProtect( $this, $legacyUser, $limit, $reason ) ) {
2064 return Status::newGood();
2065 }
2066
2067 if ( $id ) { // Protection of existing page
2068 // Only certain restrictions can cascade...
2069 $editrestriction = isset( $limit['edit'] )
2070 ? [ $limit['edit'] ]
2071 : $restrictionStore->getRestrictions( $this->mTitle, 'edit' );
2072 foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
2073 $editrestriction[$key] = 'editprotected'; // backwards compatibility
2074 }
2075 foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
2076 $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
2077 }
2078
2079 $cascadingRestrictionLevels = $services->getMainConfig()
2080 ->get( MainConfigNames::CascadingRestrictionLevels );
2081
2082 foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
2083 $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
2084 }
2085 foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
2086 $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
2087 }
2088
2089 // The schema allows multiple restrictions
2090 if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
2091 $cascade = false;
2092 }
2093
2094 // insert dummy revision to identify the page protection change as edit summary
2095 $dummyRevisionRecord = $this->insertNullProtectionRevision(
2096 $revCommentMsg,
2097 $limit,
2098 $expiry,
2099 $cascade,
2100 $reason,
2101 $user
2102 );
2103
2104 if ( $dummyRevisionRecord === null ) {
2105 return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
2106 }
2107
2108 $logRelationsField = 'pr_id';
2109
2110 // T214035: Avoid deadlock on MySQL.
2111 // Do a DELETE by primary key (pr_id) for any existing protection rows.
2112 // On MySQL and derivatives, unconditionally deleting by page ID (pr_page) would.
2113 // place a gap lock if there are no matching rows. This can deadlock when another
2114 // thread modifies protection settings for page IDs in the same gap.
2115 $existingProtectionIds = $dbw->newSelectQueryBuilder()
2116 ->select( 'pr_id' )
2117 ->from( 'page_restrictions' )
2118 ->where( [ 'pr_page' => $id, 'pr_type' => array_map( 'strval', array_keys( $limit ) ) ] )
2119 ->caller( __METHOD__ )->fetchFieldValues();
2120
2121 if ( $existingProtectionIds ) {
2122 $dbw->newDeleteQueryBuilder()
2123 ->deleteFrom( 'page_restrictions' )
2124 ->where( [ 'pr_id' => $existingProtectionIds ] )
2125 ->caller( __METHOD__ )->execute();
2126 }
2127
2128 // Update restrictions table
2129 foreach ( $limit as $action => $restrictions ) {
2130 if ( $restrictions != '' ) {
2131 $cascadeValue = ( $cascade && $action == 'edit' ) ? 1 : 0;
2132 $dbw->newInsertQueryBuilder()
2133 ->insertInto( 'page_restrictions' )
2134 ->row( [
2135 'pr_page' => $id,
2136 'pr_type' => $action,
2137 'pr_level' => $restrictions,
2138 'pr_cascade' => $cascadeValue,
2139 'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
2140 ] )
2141 ->caller( __METHOD__ )->execute();
2142 $logRelationsValues[] = $dbw->insertId();
2143 $logParamsDetails[] = [
2144 'type' => $action,
2145 'level' => $restrictions,
2146 'expiry' => $expiry[$action],
2147 'cascade' => (bool)$cascadeValue,
2148 ];
2149 }
2150 }
2151 } else { // Protection of non-existing page (also known as "title protection")
2152 // Cascade protection is meaningless in this case
2153 $cascade = false;
2154
2155 if ( $limit['create'] != '' ) {
2156 $commentFields = $services->getCommentStore()->insert( $dbw, 'pt_reason', $reason );
2157 $dbw->newReplaceQueryBuilder()
2158 ->table( 'protected_titles' )
2159 ->uniqueIndexFields( [ 'pt_namespace', 'pt_title' ] )
2160 ->rows( [
2161 'pt_namespace' => $this->mTitle->getNamespace(),
2162 'pt_title' => $this->mTitle->getDBkey(),
2163 'pt_create_perm' => $limit['create'],
2164 'pt_timestamp' => $dbw->timestamp(),
2165 'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
2166 'pt_user' => $user->getId(),
2167 ] + $commentFields )
2168 ->caller( __METHOD__ )->execute();
2169 $logParamsDetails[] = [
2170 'type' => 'create',
2171 'level' => $limit['create'],
2172 'expiry' => $expiry['create'],
2173 ];
2174 } else {
2175 $dbw->newDeleteQueryBuilder()
2176 ->deleteFrom( 'protected_titles' )
2177 ->where( [
2178 'pt_namespace' => $this->mTitle->getNamespace(),
2179 'pt_title' => $this->mTitle->getDBkey()
2180 ] )
2181 ->caller( __METHOD__ )->execute();
2182 }
2183 }
2184
2185 $this->getHookRunner()->onArticleProtectComplete( $this, $legacyUser, $limit, $reason );
2186
2187 $restrictionStore->flushRestrictions( $this->mTitle );
2188
2189 InfoAction::invalidateCache( $this->mTitle );
2190
2191 if ( $logAction == 'unprotect' ) {
2192 $params = [];
2193 } else {
2194 $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
2195 $params = [
2196 '4::description' => $protectDescriptionLog, // parameter for IRC
2197 '5:bool:cascade' => $cascade,
2198 'details' => $logParamsDetails, // parameter for localize and api
2199 ];
2200 }
2201
2202 // Update the protection log
2203 $logEntry = new ManualLogEntry( 'protect', $logAction );
2204 $logEntry->setTarget( $this->mTitle );
2205 $logEntry->setComment( $reason );
2206 $logEntry->setPerformer( $user );
2207 $logEntry->setParameters( $params );
2208 if ( $dummyRevisionRecord !== null ) {
2209 $logEntry->setAssociatedRevId( $dummyRevisionRecord->getId() );
2210 }
2211 $logEntry->addTags( $tags );
2212 if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
2213 $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
2214 }
2215 $logId = $logEntry->insert();
2216 $logEntry->publish( $logId );
2217
2218 $event = new PageProtectionChangedEvent(
2219 $this,
2220 $restrictionMapBefore,
2221 $restrictionMapAfter,
2222 $expiry,
2223 $cascade,
2224 $user,
2225 $reason,
2226 $tags
2227 );
2228
2229 $dispatcher = MediaWikiServices::getInstance()->getDomainEventDispatcher();
2230 $dispatcher->dispatch( $event, $services->getConnectionProvider() );
2231
2232 return Status::newGood( $logId );
2233 }
2234
2256 Assert::precondition(
2257 $this->derivedDataUpdater !== null,
2258 'There is no ongoing update tracked by this instance of WikiPage!'
2259 );
2260
2261 return $this->derivedDataUpdater;
2262 }
2263
2279 string $revCommentMsg,
2280 array $limit,
2281 array $expiry,
2282 bool $cascade,
2283 string $reason,
2284 UserIdentity $user
2285 ): ?RevisionRecord {
2286 // Prepare a dummy revision to be added to the history
2287 $editComment = wfMessage(
2288 $revCommentMsg,
2289 $this->mTitle->getPrefixedText(),
2290 $user->getName()
2291 )->inContentLanguage()->text();
2292 if ( $reason ) {
2293 $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
2294 }
2295 $protectDescription = $this->protectDescription( $limit, $expiry );
2296 if ( $protectDescription ) {
2297 $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2298 $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )
2299 ->inContentLanguage()->text();
2300 }
2301 if ( $cascade ) {
2302 $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2303 $editComment .= wfMessage( 'brackets' )->params(
2304 wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
2305 )->inContentLanguage()->text();
2306 }
2307
2308 return $this->newPageUpdater( $user )
2309 ->setCause( PageUpdater::CAUSE_PROTECTION_CHANGE )
2310 ->saveDummyRevision( $editComment, EDIT_SILENT | EDIT_MINOR );
2311 }
2312
2317 protected function formatExpiry( $expiry ) {
2318 if ( $expiry != 'infinity' ) {
2319 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2320 return wfMessage(
2321 'protect-expiring',
2322 $contLang->timeanddate( $expiry, false, false ),
2323 $contLang->date( $expiry, false, false ),
2324 $contLang->time( $expiry, false, false )
2325 )->inContentLanguage()->text();
2326 } else {
2327 return wfMessage( 'protect-expiry-indefinite' )
2328 ->inContentLanguage()->text();
2329 }
2330 }
2331
2339 public function protectDescription( array $limit, array $expiry ) {
2340 $protectDescription = '';
2341
2342 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2343 # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
2344 # All possible message keys are listed here for easier grepping:
2345 # * restriction-create
2346 # * restriction-edit
2347 # * restriction-move
2348 # * restriction-upload
2349 $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
2350 # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
2351 # with '' filtered out. All possible message keys are listed below:
2352 # * protect-level-autoconfirmed
2353 # * protect-level-sysop
2354 $restrictionsText = wfMessage( 'protect-level-' . $restrictions )
2355 ->inContentLanguage()->text();
2356
2357 $expiryText = $this->formatExpiry( $expiry[$action] );
2358
2359 if ( $protectDescription !== '' ) {
2360 $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2361 }
2362 $protectDescription .= wfMessage( 'protect-summary-desc' )
2363 ->params( $actionText, $restrictionsText, $expiryText )
2364 ->inContentLanguage()->text();
2365 }
2366
2367 return $protectDescription;
2368 }
2369
2381 public function protectDescriptionLog( array $limit, array $expiry ) {
2382 $protectDescriptionLog = '';
2383
2384 $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark();
2385 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2386 $expiryText = $this->formatExpiry( $expiry[$action] );
2387 $protectDescriptionLog .=
2388 $dirMark .
2389 "[$action=$restrictions] ($expiryText)";
2390 }
2391
2392 return trim( $protectDescriptionLog );
2393 }
2394
2409 public function isBatchedDelete( $safetyMargin = 0 ) {
2410 $deleteRevisionsBatchSize = MediaWikiServices::getInstance()
2411 ->getMainConfig()->get( MainConfigNames::DeleteRevisionsBatchSize );
2412
2413 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
2414 $revCount = $this->getRevisionStore()->countRevisionsByPageId( $dbr, $this->getId() );
2415 $revCount += $safetyMargin;
2416
2417 return $revCount >= $deleteRevisionsBatchSize;
2418 }
2419
2448 public function doDeleteArticleReal(
2449 $reason, UserIdentity $deleter, $suppress = false, $u1 = null, &$error = '', $u2 = null,
2450 $tags = [], $logsubtype = 'delete', $immediate = false
2451 ) {
2452 $services = MediaWikiServices::getInstance();
2453 $deletePage = $services->getDeletePageFactory()->newDeletePage(
2454 $this,
2455 $services->getUserFactory()->newFromUserIdentity( $deleter )
2456 );
2457
2458 $status = $deletePage
2459 ->setSuppress( $suppress )
2460 ->setTags( $tags ?: [] )
2461 ->setLogSubtype( $logsubtype )
2462 ->forceImmediate( $immediate )
2463 ->keepLegacyHookErrorsSeparate()
2464 ->deleteUnsafe( $reason );
2465 $error = $deletePage->getLegacyHookErrors();
2466 if ( $status->isGood() ) {
2467 // BC with old return format
2468 if ( $deletePage->deletionsWereScheduled()[DeletePage::PAGE_BASE] ) {
2469 $status->warning( 'delete-scheduled', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
2470 } else {
2471 // @phan-suppress-next-line PhanTypeMismatchProperty Changing the type of the status parameter
2472 $status->value = $deletePage->getSuccessfulDeletionsIDs()[DeletePage::PAGE_BASE];
2473 }
2474 }
2475 return $status;
2476 }
2477
2484 public function lockAndGetLatest() {
2485 $dbw = $this->getConnectionProvider()->getPrimaryDatabase();
2486 return (int)$dbw->newSelectQueryBuilder()
2487 ->select( 'page_latest' )
2488 ->forUpdate()
2489 ->from( 'page' )
2490 ->where( [
2491 'page_id' => $this->getId(),
2492 // Typically page_id is enough, but some code might try to do
2493 // updates assuming the title is the same, so verify that
2494 'page_namespace' => $this->getTitle()->getNamespace(),
2495 'page_title' => $this->getTitle()->getDBkey()
2496 ] )
2497 ->caller( __METHOD__ )->fetchField();
2498 }
2499
2513 public static function onArticleCreate( Title $title, $maybeIsRedirect = true ) {
2514 // TODO: move this into a PageEventEmitter service
2515
2516 // Update existence markers on article/talk tabs...
2517 $other = $title->getOtherPage();
2518
2519 $services = MediaWikiServices::getInstance();
2520 $hcu = $services->getHTMLCacheUpdater();
2521 $hcu->purgeTitleUrls( [ $title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2522
2523 $title->touchLinks();
2524 $services->getRestrictionStore()->deleteCreateProtection( $title );
2525
2526 $services->getLinkCache()->invalidateTitle( $title );
2527
2528 DeferredUpdates::addCallableUpdate(
2529 static function () use ( $title, $maybeIsRedirect ) {
2530 self::queueBacklinksJobs( $title, true, $maybeIsRedirect, 'create-page' );
2531 }
2532 );
2533
2534 if ( $title->getNamespace() === NS_CATEGORY ) {
2535 // Load the Category object, which will schedule a job to create
2536 // the category table row if necessary. Checking a replica DB is ok
2537 // here, in the worst case it'll run an unnecessary recount job on
2538 // a category that probably doesn't have many members.
2539 Category::newFromTitle( $title )->getID();
2540 }
2541 }
2542
2551 public static function onArticleDelete( Title $title ) {
2552 // TODO: move this into a PageEventEmitter service
2553
2554 // Update existence markers on article/talk tabs...
2555 $other = $title->getOtherPage();
2556
2557 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2558 $hcu->purgeTitleUrls( [ $title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2559
2560 $title->touchLinks();
2561
2562 $services = MediaWikiServices::getInstance();
2563 $services->getLinkCache()->invalidateTitle( $title );
2564
2565 InfoAction::invalidateCache( $title );
2566
2567 // Invalidate caches of articles which include this page
2568 DeferredUpdates::addCallableUpdate( static function () use ( $title ) {
2569 self::queueBacklinksJobs( $title, true, true, 'delete-page' );
2570 } );
2571
2572 // TODO: Move to ChangeTrackingEventIngress when ready,
2573 // but make sure it happens on deletions and page moves by adding
2574 // the appropriate assertions to ChangeTrackingEventIngressSpyTrait.
2575 // Messages
2576 // User talk pages
2577 if ( $title->getNamespace() === NS_USER_TALK ) {
2578 $user = User::newFromName( $title->getText(), false );
2579 if ( $user ) {
2580 MediaWikiServices::getInstance()
2581 ->getTalkPageNotificationManager()
2582 ->removeUserHasNewMessages( $user );
2583 }
2584 }
2585
2586 // TODO: Create MediaEventIngress and move this there.
2587 // Image redirects
2588 $services->getRepoGroup()->getLocalRepo()->invalidateImageRedirect( $title );
2589
2590 // Purge cross-wiki cache entities referencing this page
2591 self::purgeInterwikiCheckKey( $title );
2592 }
2593
2604 public static function onArticleEdit(
2605 Title $title,
2606 ?RevisionRecord $revRecord = null,
2607 $slotsChanged = null,
2608 $maybeRedirectChanged = true
2609 ) {
2610 // TODO: move this into a PageEventEmitter service
2611
2612 DeferredUpdates::addCallableUpdate(
2613 static function () use ( $title, $slotsChanged, $maybeRedirectChanged ) {
2614 self::queueBacklinksJobs(
2615 $title,
2616 $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ),
2617 $maybeRedirectChanged,
2618 'edit-page'
2619 );
2620 }
2621 );
2622
2623 $services = MediaWikiServices::getInstance();
2624 $services->getLinkCache()->invalidateTitle( $title );
2625
2626 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2627 $hcu->purgeTitleUrls( $title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2628
2629 // Purge ?action=info cache
2630 $revid = $revRecord ? $revRecord->getId() : null;
2631 DeferredUpdates::addCallableUpdate( static function () use ( $title, $revid ) {
2632 InfoAction::invalidateCache( $title, $revid );
2633 } );
2634
2635 // Purge cross-wiki cache entities referencing this page
2636 self::purgeInterwikiCheckKey( $title );
2637 }
2638
2639 private static function queueBacklinksJobs(
2640 Title $title, bool $mainSlotChanged, bool $maybeRedirectChanged, string $causeAction
2641 ) {
2642 $services = MediaWikiServices::getInstance();
2643 $backlinkCache = $services->getBacklinkCacheFactory()->getBacklinkCache( $title );
2644
2645 $jobs = [];
2646 if ( $mainSlotChanged
2647 && $backlinkCache->hasLinks( 'templatelinks' )
2648 ) {
2649 // Invalidate caches of articles which include this page.
2650 // Only for the main slot, because only the main slot is transcluded.
2651 // TODO: MCR: not true for TemplateStyles! [SlotHandler]
2652 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2653 $title,
2654 'templatelinks',
2655 [ 'causeAction' => $causeAction ]
2656 );
2657 }
2658 // Images
2659 if ( $maybeRedirectChanged && $title->getNamespace() === NS_FILE
2660 && $backlinkCache->hasLinks( 'imagelinks' )
2661 ) {
2662 // Process imagelinks in case the redirect target has changed
2663 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2664 $title,
2665 'imagelinks',
2666 [ 'causeAction' => $causeAction ]
2667 );
2668 }
2669 // Invalidate the caches of all pages which redirect here
2670 if ( $backlinkCache->hasLinks( 'redirect' ) ) {
2671 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2672 $title,
2673 'redirect',
2674 [ 'causeAction' => $causeAction ]
2675 );
2676 }
2677 if ( $jobs ) {
2678 $services->getJobQueueGroup()->push( $jobs );
2679 }
2680 }
2681
2685 private static function purgeInterwikiCheckKey( Title $title ) {
2686 $enableScaryTranscluding = MediaWikiServices::getInstance()->getMainConfig()->get(
2687 MainConfigNames::EnableScaryTranscluding );
2688
2689 if ( !$enableScaryTranscluding ) {
2690 return; // @todo: perhaps this wiki is only used as a *source* for content?
2691 }
2692
2693 DeferredUpdates::addCallableUpdate( static function () use ( $title ) {
2694 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2695 $cache->resetCheckKey(
2696 // Do not include the namespace since there can be multiple aliases to it
2697 // due to different namespace text definitions on different wikis. This only
2698 // means that some cache invalidations happen that are not strictly needed.
2699 $cache->makeGlobalKey(
2700 'interwiki-page',
2701 WikiMap::getCurrentWikiDbDomain()->getId(),
2702 $title->getDBkey()
2703 )
2704 );
2705 } );
2706 }
2707
2714 public function getCategories() {
2715 $services = MediaWikiServices::getInstance();
2716 $id = $this->getId();
2717 if ( $id == 0 ) {
2718 return $services->getTitleFactory()->newTitleArrayFromResult( new FakeResultWrapper( [] ) );
2719 }
2720
2721 $dbr = $services->getConnectionProvider()->getReplicaDatabase( CategoryLinksTable::VIRTUAL_DOMAIN );
2722 $res = $dbr->newSelectQueryBuilder()
2723 ->select( [ 'page_title' => 'lt_title', 'page_namespace' => (string)NS_CATEGORY ] )
2724 ->from( 'categorylinks' )
2725 ->join( 'linktarget', null, [ 'cl_target_id = lt_id', 'lt_namespace = ' . NS_CATEGORY ] )
2726 ->where( [ 'cl_from' => $id ] )
2727 ->caller( __METHOD__ )
2728 ->fetchResultSet();
2729
2730 return $services->getTitleFactory()->newTitleArrayFromResult( $res );
2731 }
2732
2739 public function getHiddenCategories() {
2740 $id = $this->getId();
2741
2742 if ( $id == 0 ) {
2743 return [];
2744 }
2745
2746 $categoryLinksDb = $this->getConnectionProvider()->getReplicaDatabase( CategoryLinksTable::VIRTUAL_DOMAIN );
2747 $categoryTitles = $categoryLinksDb->newSelectQueryBuilder()
2748 ->select( 'lt_title' )
2749 ->from( 'categorylinks' )
2750 ->join( 'linktarget', null, 'cl_target_id = lt_id' )
2751 ->where( [ 'cl_from' => $id, 'lt_namespace' => NS_CATEGORY ] )
2752 ->caller( __METHOD__ )
2753 ->fetchFieldValues();
2754
2755 if ( $categoryTitles === [] ) {
2756 return [];
2757 }
2758
2759 $dbr = $this->getConnectionProvider()->getReplicaDatabase();
2760 $hiddenTitles = $dbr->newSelectQueryBuilder()
2761 ->select( [ 'page_title' ] )
2762 ->from( 'page_props' )
2763 ->join( 'page', null, 'page_id = pp_page' )
2764 ->where( [
2765 'pp_propname' => 'hiddencat',
2766 'page_namespace' => NS_CATEGORY,
2767 'page_title' => $categoryTitles
2768 ] )
2769 ->caller( __METHOD__ )
2770 ->fetchFieldValues();
2771
2772 $result = [];
2773 foreach ( $categoryTitles as $title ) {
2774 if ( in_array( $title, $hiddenTitles ) ) {
2775 $result[] = Title::makeTitle( NS_CATEGORY, $title );
2776 }
2777 }
2778
2779 return $result;
2780 }
2781
2788 public function getAutoDeleteReason() {
2789 return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle() );
2790 }
2791
2804 public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2805 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
2806 return;
2807 }
2808
2809 if ( !$this->getHookRunner()->onOpportunisticLinksUpdate( $this,
2810 $this->mTitle, $parserOutput )
2811 ) {
2812 return;
2813 }
2814
2815 $config = MediaWikiServices::getInstance()->getMainConfig();
2816
2817 $params = [
2818 'isOpportunistic' => true,
2819 'rootJobTimestamp' => $parserOutput->getCacheTime()
2820 ];
2821
2822 if ( MediaWikiServices::getInstance()->getRestrictionStore()->areRestrictionsCascading( $this->mTitle ) ) {
2823 // In general, MediaWiki does not re-run LinkUpdate (e.g. for search index, category
2824 // listings, and backlinks for Whatlinkshere), unless either the page was directly
2825 // edited, or was re-generate following a template edit propagating to an affected
2826 // page. As such, during page views when there is no valid ParserCache entry,
2827 // we re-parse and save, but leave indexes as-is.
2828 //
2829 // We make an exception for pages that have cascading protection (perhaps for a wiki's
2830 // "Main Page"). When such page is re-parsed on-demand after a parser cache miss, we
2831 // queue a high-priority LinksUpdate job, to ensure that we really protect all
2832 // content that is currently transcluded onto the page. This is important, because
2833 // wikitext supports conditional statements based on the current time, which enables
2834 // transcluding of a different subpage based on which day it is, and then show that
2835 // information on the Main Page, without the Main Page itself being edited.
2836 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush(
2837 RefreshLinksJob::newPrioritized( $this->mTitle, $params )
2838 );
2839 } elseif (
2840 (
2841 // "Dynamic" content (eg time/random magic words)
2842 !$config->get( MainConfigNames::MiserMode ) &&
2843 $parserOutput->hasReducedExpiry()
2844 )
2845 ||
2846 (
2847 // Asynchronous content
2848 $config->get( MainConfigNames::ParserCacheAsyncRefreshJobs ) &&
2849 $parserOutput->getOutputFlag( ParserOutputFlags::HAS_ASYNC_CONTENT ) &&
2850 !$parserOutput->getOutputFlag( ParserOutputFlags::ASYNC_NOT_READY )
2851 )
2852 ) {
2853 // Assume the output contains "dynamic" time/random based magic words
2854 // or asynchronous content that wasn't "ready" the first time the
2855 // page was parsed.
2856 // Only update pages that expired due to dynamic content and NOT due to edits
2857 // to referenced templates/files. When the cache expires due to dynamic content,
2858 // page_touched is unchanged. We want to avoid triggering redundant jobs due to
2859 // views of pages that were just purged via HTMLCacheUpdateJob. In that case, the
2860 // template/file edit already triggered recursive RefreshLinksJob jobs.
2861 if ( $this->getLinksTimestamp() > $this->getTouched() ) {
2862 // If a page is uncacheable, do not keep spamming a job for it.
2863 // Although it would be de-duplicated, it would still waste I/O.
2864 $services = MediaWikiServices::getInstance()->getObjectCacheFactory();
2865 $cache = $services->getLocalClusterInstance();
2866 $key = $cache->makeKey( 'dynamic-linksupdate', 'last', $this->getId() );
2867 $ttl = max( $parserOutput->getCacheExpiry(), 3600 );
2868 if ( $cache->add( $key, time(), $ttl ) ) {
2869 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush(
2870 RefreshLinksJob::newDynamic( $this->mTitle, $params )
2871 );
2872 }
2873 }
2874 }
2875 }
2876
2884 public function isLocal() {
2885 return true;
2886 }
2887
2897 public function getWikiDisplayName() {
2898 $sitename = MediaWikiServices::getInstance()->getMainConfig()->get(
2899 MainConfigNames::Sitename );
2900 return $sitename;
2901 }
2902
2911 public function getSourceURL() {
2912 return $this->getTitle()->getCanonicalURL();
2913 }
2914
2921 public function __wakeup() {
2922 // Make sure we re-fetch the latest state from the database.
2923 // In particular, the latest revision may have changed.
2924 // As a side-effect, this makes sure mLastRevision doesn't
2925 // end up being an instance of the old Revision class (see T259181),
2926 // especially since that class was removed entirely in 1.37.
2927 $this->clear();
2928 }
2929
2934 public function getNamespace(): int {
2935 return $this->getTitle()->getNamespace();
2936 }
2937
2942 public function getDBkey(): string {
2943 return $this->getTitle()->getDBkey();
2944 }
2945
2950 public function getWikiId() {
2951 return $this->getTitle()->getWikiId();
2952 }
2953
2958 public function canExist(): bool {
2959 return true;
2960 }
2961
2966 public function __toString(): string {
2967 return $this->mTitle->__toString();
2968 }
2969
2974 public function isSamePageAs( PageReference $other ): bool {
2975 // NOTE: keep in sync with PageReferenceValue::isSamePageAs()!
2976 return $this->getWikiId() === $other->getWikiId()
2977 && $this->getNamespace() === $other->getNamespace()
2978 && $this->getDBkey() === $other->getDBkey();
2979 }
2980
2993 // TODO: replace individual member fields with a PageRecord instance that is always present
2994
2995 if ( !$this->mDataLoaded ) {
2996 $this->loadPageData();
2997 }
2998
2999 Assert::precondition(
3000 $this->exists(),
3001 'This WikiPage instance does not represent an existing page: ' . $this->mTitle
3002 );
3003
3004 return new PageStoreRecord(
3005 (object)[
3006 'page_id' => $this->getId(),
3007 'page_namespace' => $this->mTitle->getNamespace(),
3008 'page_title' => $this->mTitle->getDBkey(),
3009 'page_latest' => $this->mLatest,
3010 'page_is_new' => $this->mIsNew ? 1 : 0,
3011 'page_is_redirect' => $this->mPageIsRedirectField ? 1 : 0,
3012 'page_touched' => $this->getTouched(),
3013 'page_lang' => $this->getLanguage()
3014 ],
3015 PageIdentity::LOCAL
3016 );
3017 }
3018
3022 private function getConnectionProvider(): \Wikimedia\Rdbms\IConnectionProvider {
3023 return MediaWikiServices::getInstance()->getConnectionProvider();
3024 }
3025
3026}
3027
3029class_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:29
Value object for a comment stored by CommentStore.
Base class for content handling.
Defer callable updates to run later in the PHP process.
Represents information returned by WikiPage::prepareContentForEdit()
Job to purge the HTML/file cache for all pages that link to or use another page or file.
Job to update link tables for rerendered wiki pages.
Create PSR-3 logger objects.
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
getMainConfig()
Returns the Config object that provides configuration for MediaWiki core.
static getInstance()
Returns the global default instance of the top level service locator.
Domain event representing changes to page protection (aka restriction levels).
Immutable data record representing an editable page on a wiki.
Base representation for an editable wiki page.
Definition WikiPage.php: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:977
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.
Definition WikiPage.php:999
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:989
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( $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:1717
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition Title.php:3328
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1037
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:1010
Class to walk into a list of User objects.
Definition UserArray.php:19
User class for the MediaWiki software.
Definition User.php:130
Library for creating and parsing MW-style timestamps.
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:19
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
Build SELECT queries with a fluent interface.
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'DefaultLockManager'=> null, 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'EnableSectionShare'=> false, 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'RestrictUserPageEditing' => false, 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'DefaultLockManager' => [ 'string', 'null', ], 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', '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', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'RestrictUserPageEditing' => 'boolean', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'ReauthenticateForActions' => 'object', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RestrictedTagViewRights' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'mode' => [ 'type' => 'string', ], ], 'required' => [ 'mode', ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
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