MediaWiki master
WikiPage.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Page;
8
9use BadMethodCallException;
10use InvalidArgumentException;
17use MediaWiki\DAO\WikiAwareEntityTrait;
22use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
56use RuntimeException;
57use stdClass;
58use Stringable;
59use Wikimedia\Assert\Assert;
60use Wikimedia\Assert\PreconditionException;
61use Wikimedia\NonSerializable\NonSerializableTrait;
68use Wikimedia\Timestamp\TimestampFormat as TS;
69
82class WikiPage implements Stringable, Page, PageRecord {
83 use NonSerializableTrait;
84 use ProtectedHookAccessorTrait;
85 use WikiAwareEntityTrait;
86
87 // Constants for $mDataLoadedFrom and related
88
93 protected $mTitle;
94
99 protected $mDataLoaded = false;
100
105 private $mPageIsRedirectField = false;
106
110 private $mIsNew = false;
111
116 protected $mLatest = false;
117
122 protected $mPreparedEdit = false;
123
127 protected $mId = null;
128
132 protected $mDataLoadedFrom = IDBAccessObject::READ_NONE;
133
137 private $mLastRevision = null;
138
142 protected $mTimestamp = '';
143
147 protected $mTouched = '19700101000000';
148
152 protected $mLanguage = null;
153
157 protected $mLinksUpdated = '19700101000000';
158
162 private $derivedDataUpdater = null;
163
164 public function __construct( PageIdentity $pageIdentity ) {
165 $pageIdentity->assertWiki( PageIdentity::LOCAL );
166
167 // TODO: remove the need for casting to Title.
168 $title = Title::newFromPageIdentity( $pageIdentity );
169 if ( !$title->canExist() ) {
170 throw new InvalidArgumentException( "WikiPage constructed on a Title that cannot exist as a page: $title" );
171 }
172
173 $this->mTitle = $title;
174 }
175
180 public function __clone() {
181 $this->mTitle = clone $this->mTitle;
182 }
183
190 public static function convertSelectType( $type ) {
191 switch ( $type ) {
192 case 'fromdb':
193 return IDBAccessObject::READ_NORMAL;
194 case 'fromdbmaster':
195 return IDBAccessObject::READ_LATEST;
196 case 'forupdate':
197 return IDBAccessObject::READ_LOCKING;
198 default:
199 // It may already be an integer or whatever else
200 return $type;
201 }
202 }
203
204 private function getPageUpdaterFactory(): PageUpdaterFactory {
205 return MediaWikiServices::getInstance()->getPageUpdaterFactory();
206 }
207
211 private function getRevisionStore() {
213 }
214
218 private function getDBLoadBalancer() {
219 return MediaWikiServices::getInstance()->getDBLoadBalancer();
220 }
221
228 public function getActionOverrides() {
229 return $this->getContentHandler()->getActionOverrides();
230 }
231
241 public function getContentHandler() {
242 $factory = MediaWikiServices::getInstance()->getContentHandlerFactory();
243 return $factory->getContentHandler( $this->getContentModel() );
244 }
245
250 public function getTitle(): Title {
251 return $this->mTitle;
252 }
253
258 public function clear() {
259 $this->mDataLoaded = false;
260 $this->mDataLoadedFrom = IDBAccessObject::READ_NONE;
261
262 $this->clearCacheFields();
263 }
264
269 protected function clearCacheFields() {
270 $this->mId = null;
271 $this->mPageIsRedirectField = false;
272 $this->mLastRevision = null; // Latest revision
273 $this->mTouched = '19700101000000';
274 $this->mLanguage = null;
275 $this->mLinksUpdated = '19700101000000';
276 $this->mTimestamp = '';
277 $this->mIsNew = false;
278 $this->mLatest = false;
279 // T59026: do not clear $this->derivedDataUpdater since getDerivedDataUpdater() already
280 // checks the requested rev ID and content against the cached one. For most
281 // content types, the output should not change during the lifetime of this cache.
282 // Clearing it can cause extra parses on edit for no reason.
283 }
284
290 public function clearPreparedEdit() {
291 $this->mPreparedEdit = false;
292 }
293
307 public static function getQueryInfo() {
308 $pageLanguageUseDB = MediaWikiServices::getInstance()->getMainConfig()->get(
309 MainConfigNames::PageLanguageUseDB );
310
311 $ret = [
312 'tables' => [ 'page' ],
313 'fields' => [
314 'page_id',
315 'page_namespace',
316 'page_title',
317 'page_is_redirect',
318 'page_is_new',
319 'page_random',
320 'page_touched',
321 'page_links_updated',
322 'page_latest',
323 'page_len',
324 'page_content_model',
325 ],
326 'joins' => [],
327 ];
328
329 if ( $pageLanguageUseDB ) {
330 $ret['fields'][] = 'page_lang';
331 }
332
333 return $ret;
334 }
335
343 protected function pageData( $dbr, $conditions, $options = [] ) {
344 $pageQuery = self::getQueryInfo();
345
346 $this->getHookRunner()->onArticlePageDataBefore(
347 $this, $pageQuery['fields'], $pageQuery['tables'], $pageQuery['joins'] );
348
349 $row = $dbr->newSelectQueryBuilder()
350 ->queryInfo( $pageQuery )
351 ->where( $conditions )
352 ->caller( __METHOD__ )
353 ->options( $options )
354 ->fetchRow();
355
356 $this->getHookRunner()->onArticlePageDataAfter( $this, $row );
357
358 return $row;
359 }
360
370 public function pageDataFromTitle( $dbr, $title, $recency = IDBAccessObject::READ_NORMAL ) {
371 if ( !$title->canExist() ) {
372 return false;
373 }
374 $options = [];
375 if ( ( $recency & IDBAccessObject::READ_EXCLUSIVE ) == IDBAccessObject::READ_EXCLUSIVE ) {
376 $options[] = 'FOR UPDATE';
377 } elseif ( ( $recency & IDBAccessObject::READ_LOCKING ) == IDBAccessObject::READ_LOCKING ) {
378 $options[] = 'LOCK IN SHARE MODE';
379 }
380
381 return $this->pageData( $dbr, [
382 'page_namespace' => $title->getNamespace(),
383 'page_title' => $title->getDBkey() ], $options );
384 }
385
394 public function pageDataFromId( $dbr, $id, $options = [] ) {
395 return $this->pageData( $dbr, [ 'page_id' => $id ], $options );
396 }
397
412 public function loadPageData( $from = IDBAccessObject::READ_NORMAL ) {
413 $from = self::convertSelectType( $from );
414 if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
415 // We already have the data from the correct location, no need to load it twice.
416 return;
417 }
418
419 if ( is_int( $from ) ) {
420 $loadBalancer = $this->getDBLoadBalancer();
421 if ( ( $from & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST ) {
422 $index = DB_PRIMARY;
423 } else {
424 $index = DB_REPLICA;
425 }
426 $db = $loadBalancer->getConnection( $index );
427 $data = $this->pageDataFromTitle( $db, $this->mTitle, $from );
428
429 if ( !$data
430 && $index == DB_REPLICA
431 && $loadBalancer->hasReplicaServers()
432 && $loadBalancer->hasOrMadeRecentPrimaryChanges()
433 ) {
434 $from = IDBAccessObject::READ_LATEST;
435 $db = $loadBalancer->getConnection( DB_PRIMARY );
436 $data = $this->pageDataFromTitle( $db, $this->mTitle, $from );
437 }
438 } else {
439 // No idea from where the caller got this data, assume replica DB.
440 $data = $from;
441 $from = IDBAccessObject::READ_NORMAL;
442 }
443
444 $this->loadFromRow( $data, $from );
445 }
446
461 public function wasLoadedFrom( $from ) {
462 $from = self::convertSelectType( $from );
463
464 if ( !is_int( $from ) ) {
465 // No idea from where the caller got this data, assume replica DB.
466 $from = IDBAccessObject::READ_NORMAL;
467 }
468
469 if ( $from <= $this->mDataLoadedFrom ) {
470 return true;
471 }
472
473 return false;
474 }
475
489 public function loadFromRow( $data, $from ) {
490 $from = self::convertSelectType( $from );
491
492 $lc = MediaWikiServices::getInstance()->getLinkCache();
493 $lc->clearLink( $this->mTitle );
494
495 if ( $data ) {
496 $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
497
498 $this->mTitle->loadFromRow( $data );
499 $this->mId = intval( $data->page_id );
500 $this->mTouched = MWTimestamp::convert( TS::MW, $data->page_touched );
501 $this->mLanguage = $data->page_lang ?? null;
502 $this->mLinksUpdated = $data->page_links_updated === null
503 ? null
504 : MWTimestamp::convert( TS::MW, $data->page_links_updated );
505 $this->mPageIsRedirectField = (bool)$data->page_is_redirect;
506 $this->mIsNew = (bool)( $data->page_is_new ?? 0 );
507 $this->mLatest = intval( $data->page_latest );
508 // T39225: $latest may no longer match the cached latest RevisionRecord object.
509 // Double-check the ID of any cached latest RevisionRecord object for consistency.
510 // T400380: since a DB row had to be loaded in, clear the latest RevisionRecord
511 // object if it can from object cache (e.g. it is RevisionStoreCacheRecord).
512 if (
513 $this->mLastRevision && (
514 $from > $this->mDataLoadedFrom ||
515 $this->mLastRevision->getId() != $this->mLatest
516 )
517 ) {
518 $this->mLastRevision = null;
519 $this->mTimestamp = '';
520 }
521 } else {
522 $lc->addBadLinkObj( $this->mTitle );
523
524 $this->mTitle->loadFromRow( false );
525
526 $this->clearCacheFields();
527
528 $this->mId = 0;
529 }
530
531 $this->mDataLoaded = true;
532 $this->mDataLoadedFrom = $from;
533 }
534
540 public function getId( $wikiId = self::LOCAL ): int {
541 $this->assertWiki( $wikiId );
542
543 if ( !$this->mDataLoaded ) {
544 $this->loadPageData();
545 }
546 return $this->mId;
547 }
548
552 public function exists(): bool {
553 if ( !$this->mDataLoaded ) {
554 $this->loadPageData();
555 }
556 return $this->mId > 0;
557 }
558
567 public function hasViewableContent() {
568 return $this->mTitle->isKnown();
569 }
570
577 public function isRedirect() {
578 $this->loadPageData();
579 if ( $this->mPageIsRedirectField ) {
580 return MediaWikiServices::getInstance()->getRedirectLookup()
581 ->getRedirectTarget( $this->getTitle() ) !== null;
582 }
583
584 return false;
585 }
586
595 public function isNew() {
596 if ( !$this->mDataLoaded ) {
597 $this->loadPageData();
598 }
599
600 return $this->mIsNew;
601 }
602
613 public function getContentModel() {
614 if ( $this->exists() ) {
615 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
616
617 return $cache->getWithSetCallback(
618 $cache->makeKey( 'page-content-model', $this->getLatest() ),
619 $cache::TTL_MONTH,
620 function () {
621 $rev = $this->getRevisionRecord();
622 if ( $rev ) {
623 // Look at the revision's actual content model
624 $slot = $rev->getSlot(
625 SlotRecord::MAIN,
626 RevisionRecord::RAW
627 );
628 return $slot->getModel();
629 } else {
630 LoggerFactory::getInstance( 'wikipage' )->warning(
631 'Page exists but has no (visible) revisions!',
632 [
633 'page-title' => $this->mTitle->getPrefixedDBkey(),
634 'page-id' => $this->getId(),
635 ]
636 );
637 return $this->mTitle->getContentModel();
638 }
639 },
640 [ 'pcTTL' => $cache::TTL_PROC_LONG ]
641 );
642 }
643
644 // use the default model for this page
645 return $this->mTitle->getContentModel();
646 }
647
652 public function checkTouched() {
653 return ( $this->exists() && !$this->isRedirect() );
654 }
655
660 public function getTouched() {
661 if ( !$this->mDataLoaded ) {
662 $this->loadPageData();
663 }
664 return $this->mTouched;
665 }
666
670 public function getLanguage() {
671 if ( !$this->mDataLoaded ) {
672 $this->loadLastEdit();
673 }
674
675 return $this->mLanguage;
676 }
677
682 public function getLinksTimestamp() {
683 if ( !$this->mDataLoaded ) {
684 $this->loadPageData();
685 }
686 return $this->mLinksUpdated;
687 }
688
694 public function getLatest( $wikiId = self::LOCAL ) {
695 $this->assertWiki( $wikiId );
696
697 if ( !$this->mDataLoaded ) {
698 $this->loadPageData();
699 }
700 return (int)$this->mLatest;
701 }
702
707 protected function loadLastEdit() {
708 if ( $this->mLastRevision !== null ) {
709 return; // already loaded
710 }
711
712 $latest = $this->getLatest();
713 if ( !$latest ) {
714 return; // page doesn't exist or is missing page_latest info
715 }
716
717 if ( $this->mDataLoadedFrom == IDBAccessObject::READ_LOCKING ) {
718 // T39225: if session S1 loads the page row FOR UPDATE, the result always
719 // includes the latest changes committed. This is true even within REPEATABLE-READ
720 // transactions, where S1 normally only sees changes committed before the first S1
721 // SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
722 // may not find it since a page row UPDATE and revision row INSERT by S2 may have
723 // happened after the first S1 SELECT.
724 // https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html#isolevel_repeatable-read
725 $revision = $this->getRevisionStore()
726 ->getRevisionByPageId( $this->getId(), $latest, IDBAccessObject::READ_LOCKING );
727 } elseif ( $this->mDataLoadedFrom == IDBAccessObject::READ_LATEST ) {
728 // Bug T93976: if page_latest was loaded from the primary DB, fetch the
729 // revision from there as well, as it may not exist yet on a replica DB.
730 // Also, this keeps the queries in the same REPEATABLE-READ snapshot.
731 $revision = $this->getRevisionStore()
732 ->getRevisionByPageId( $this->getId(), $latest, IDBAccessObject::READ_LATEST );
733 } else {
734 $revision = $this->getRevisionStore()->getKnownLatestRevision( $this->getTitle(), $latest );
735 }
736
737 if ( $revision ) {
738 $this->setLastEdit( $revision );
739 }
740 }
741
745 private function setLastEdit( RevisionRecord $revRecord ) {
746 $this->mLastRevision = $revRecord;
747 $this->mLatest = $revRecord->getId();
748 $this->mTimestamp = $revRecord->getTimestamp();
749 $this->mTouched = max( $this->mTouched, $revRecord->getTimestamp() );
750 }
751
757 public function getRevisionRecord() {
758 $this->loadLastEdit();
759 return $this->mLastRevision;
760 }
761
775 public function getContent( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
776 $this->loadLastEdit();
777 if ( $this->mLastRevision ) {
778 return $this->mLastRevision->getContent( SlotRecord::MAIN, $audience, $performer );
779 }
780 return null;
781 }
782
786 public function getTimestamp() {
787 // Check if the field has been filled by WikiPage::setTimestamp()
788 if ( !$this->mTimestamp ) {
789 $this->loadLastEdit();
790 }
791
792 return MWTimestamp::convert( TS::MW, $this->mTimestamp );
793 }
794
800 public function setTimestamp( $ts ) {
801 $this->mTimestamp = MWTimestamp::convert( TS::MW, $ts );
802 }
803
814 public function getUser( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
815 $this->loadLastEdit();
816 if ( $this->mLastRevision ) {
817 $revUser = $this->mLastRevision->getUser( $audience, $performer );
818 return $revUser ? $revUser->getId() : 0;
819 } else {
820 return -1;
821 }
822 }
823
835 public function getCreator( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
836 $revRecord = $this->getRevisionStore()->getFirstRevision( $this->getTitle() );
837 if ( $revRecord ) {
838 return $revRecord->getUser( $audience, $performer );
839 } else {
840 return null;
841 }
842 }
843
854 public function getUserText( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
855 $this->loadLastEdit();
856 if ( $this->mLastRevision ) {
857 $revUser = $this->mLastRevision->getUser( $audience, $performer );
858 return $revUser ? $revUser->getName() : '';
859 } else {
860 return '';
861 }
862 }
863
875 public function getComment( $audience = RevisionRecord::FOR_PUBLIC, ?Authority $performer = null ) {
876 $this->loadLastEdit();
877 if ( $this->mLastRevision ) {
878 $revComment = $this->mLastRevision->getComment( $audience, $performer );
879 return $revComment ? $revComment->text : '';
880 } else {
881 return '';
882 }
883 }
884
890 public function getMinorEdit() {
891 $this->loadLastEdit();
892 if ( $this->mLastRevision ) {
893 return $this->mLastRevision->isMinor();
894 } else {
895 return false;
896 }
897 }
898
915 public function isCountable( $editInfo = false ) {
916 $mwServices = MediaWikiServices::getInstance();
917 $articleCountMethod = $mwServices->getMainConfig()->get( MainConfigNames::ArticleCountMethod );
918
919 // NOTE: Keep in sync with DerivedPageDataUpdater::isCountable.
920
921 if ( !$this->mTitle->isContentPage() ) {
922 return false;
923 }
924
925 if ( $editInfo instanceof PreparedEdit ) {
926 // NOTE: only the main slot can make a page a redirect
927 $content = $editInfo->pstContent;
928 } elseif ( $editInfo instanceof PreparedUpdate ) {
929 // NOTE: only the main slot can make a page a redirect
930 $content = $editInfo->getRawContent( SlotRecord::MAIN );
931 } else {
932 $content = $this->getContent();
933 }
934
935 if ( !$content || $content->isRedirect() ) {
936 return false;
937 }
938
939 $hasLinks = null;
940
941 if ( $articleCountMethod === 'link' ) {
942 // nasty special case to avoid re-parsing to detect links
943
944 if ( $editInfo ) {
945 $hasLinks = $editInfo->output->hasLinks();
946 } else {
947 // NOTE: keep in sync with RevisionRenderer::getLinkCount
948 // NOTE: keep in sync with DerivedPageDataUpdater::isCountable
949 $dbr = $mwServices
950 ->getConnectionProvider()
951 ->getReplicaDatabase( PageLinksTable::VIRTUAL_DOMAIN );
952 $hasLinks = (bool)$dbr->newSelectQueryBuilder()
953 ->select( '1' )
954 ->from( 'pagelinks' )
955 ->where( [ 'pl_from' => $this->getId() ] )
956 ->caller( __METHOD__ )->fetchField();
957 }
958 }
959
960 // TODO: MCR: determine $hasLinks for each slot, and use that info
961 // with that slot's Content's isCountable method. That requires per-
962 // slot ParserOutput in the ParserCache, or per-slot info in the
963 // pagelinks table.
964 return $content->isCountable( $hasLinks );
965 }
966
976 public function getRedirectTarget() {
977 $target = MediaWikiServices::getInstance()->getRedirectLookup()->getRedirectTarget( $this );
978 return Title::castFromLinkTarget( $target );
979 }
980
988 public function insertRedirectEntry( LinkTarget $rt, $oldLatest = null ) {
989 return MediaWikiServices::getInstance()->getRedirectStore()
990 ->updateRedirectTarget( $this, $rt );
991 }
992
998 public function followRedirect() {
999 return $this->getRedirectURL( $this->getRedirectTarget() );
1000 }
1001
1009 public function getRedirectURL( $rt ) {
1010 if ( !$rt ) {
1011 return false;
1012 }
1013
1014 if ( $rt->isExternal() ) {
1015 if ( $rt->isLocal() ) {
1016 // Offsite wikis need an HTTP redirect.
1017 // This can be hard to reverse and may produce loops,
1018 // so they may be disabled in the site configuration.
1019 $source = $this->mTitle->getFullURL( 'redirect=no' );
1020 return $rt->getFullURL( [ 'rdfrom' => $source ] );
1021 } else {
1022 // External pages without "local" bit set are not valid
1023 // redirect targets
1024 return false;
1025 }
1026 }
1027
1028 if ( $rt->isSpecialPage() ) {
1029 // Gotta handle redirects to special pages differently:
1030 // Fill the HTTP response "Location" header and ignore the rest of the page we're on.
1031 // Some pages are not valid targets.
1032 if ( $rt->isValidRedirectTarget() ) {
1033 return $rt->getFullURL();
1034 } else {
1035 return false;
1036 }
1037 } elseif ( !$rt->isValidRedirectTarget() ) {
1038 // We somehow got a bad redirect target into the database (T278367)
1039 return false;
1040 }
1041
1042 return $rt;
1043 }
1044
1050 public function getContributors() {
1051 // @todo: This is expensive; cache this info somewhere.
1052
1053 $services = MediaWikiServices::getInstance();
1054 $dbr = $services->getConnectionProvider()->getReplicaDatabase();
1055 $actorNormalization = $services->getActorNormalization();
1056 $userIdentityLookup = $services->getUserIdentityLookup();
1057
1058 $user = $this->getUser()
1059 ? User::newFromId( $this->getUser() )
1060 : User::newFromName( $this->getUserText(), false );
1061
1062 $res = $dbr->newSelectQueryBuilder()
1063 ->select( [
1064 'user_id' => 'actor_user',
1065 'user_name' => 'actor_name',
1066 'actor_id' => 'MIN(rev_actor)',
1067 'user_real_name' => 'MIN(user_real_name)',
1068 'timestamp' => 'MAX(rev_timestamp)',
1069 ] )
1070 ->from( 'revision' )
1071 ->join( 'actor', null, 'rev_actor = actor_id' )
1072 ->leftJoin( 'user', null, 'actor_user = user_id' )
1073 ->where( [
1074 'rev_page' => $this->getId(),
1075 // The user who made the top revision gets credited as "this page was last edited by
1076 // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
1077 $dbr->expr( 'rev_actor', '!=', $actorNormalization->findActorId( $user, $dbr ) ),
1078 // Username hidden?
1079 $dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0',
1080 ] )
1081 ->groupBy( [ 'actor_user', 'actor_name' ] )
1082 ->orderBy( 'timestamp', SelectQueryBuilder::SORT_DESC )
1083 ->caller( __METHOD__ )
1084 ->fetchResultSet();
1085 return new UserArrayFromResult( $res );
1086 }
1087
1095 public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
1096 // NOTE: Keep in sync with ParserOutputAccess::shouldUseCache().
1097 // TODO: Once ParserOutputAccess is stable, deprecated this method.
1098 return $this->exists()
1099 && ( $oldId === null || $oldId === 0 || $oldId === $this->getLatest() )
1100 && $this->getContentHandler()->isParserCacheSupported();
1101 }
1102
1118 public function getParserOutput(
1119 ?ParserOptions $parserOptions = null, $oldid = null, $noCache = false
1120 ) {
1121 if ( $oldid ) {
1122 $revision = $this->getRevisionStore()->getRevisionByTitle( $this->getTitle(), $oldid );
1123
1124 if ( !$revision ) {
1125 return false;
1126 }
1127 } else {
1128 $revision = $this->getRevisionRecord();
1129 }
1130
1131 if ( !$parserOptions ) {
1132 $parserOptions = ParserOptions::newFromAnon();
1133 }
1134
1135 $options = $noCache ? ParserOutputAccess::OPT_NO_CACHE : 0;
1136
1137 $status = MediaWikiServices::getInstance()->getParserOutputAccess()->getParserOutput(
1138 $this, $parserOptions, $revision, $options
1139 );
1140 return $status->isOK() ? $status->getValue() : false; // convert null to false
1141 }
1142
1150 public function doViewUpdates(
1151 Authority $performer,
1152 $oldRev = null,
1153 $oldRevDeprecated = null
1154 ) {
1155 if ( func_num_args() > 2 ) {
1156 wfDeprecatedMsg( 'Passing $oldid to ' . __METHOD__ . ' is deprecated since 1.46.' );
1157 $oldRev = $oldRevDeprecated;
1158 }
1159
1160 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
1161 return;
1162 }
1163
1164 DeferredUpdates::addCallableUpdate(
1165 function () use ( $performer ) {
1166 // In practice, these hook handlers simply debounce into a post-send
1167 // to do their work since none of the use cases for this hook require
1168 // a blocking pre-send callback.
1169 //
1170 // TODO: Move this hook to post-send.
1171 //
1172 // For now, it is unofficially possible for an extension to use
1173 // onPageViewUpdates to try to insert JavaScript via global $wgOut.
1174 // This isn't supported (the hook doesn't pass OutputPage), and
1175 // can't be since OutputPage may be disabled or replaced on some
1176 // pages that we do support page view updates for. We also run
1177 // this hook after HTMLFileCache, which also naturally can't
1178 // support modifying OutputPage. Handlers that modify the page
1179 // may use onBeforePageDisplay instead, which runs behind
1180 // HTMLFileCache and won't run on non-OutputPage responses.
1181 $legacyUser = MediaWikiServices::getInstance()
1182 ->getUserFactory()
1183 ->newFromAuthority( $performer );
1184 $this->getHookRunner()->onPageViewUpdates( $this, $legacyUser );
1185 },
1186 DeferredUpdates::PRESEND
1187 );
1188
1189 // Update newtalk and watchlist notification status
1190 MediaWikiServices::getInstance()
1191 ->getWatchlistManager()
1192 ->clearTitleUserNotifications( $performer, $this, $oldRev );
1193 }
1194
1201 public function doPurge() {
1202 if ( !$this->getHookRunner()->onArticlePurge( $this ) ) {
1203 return false;
1204 }
1205
1206 $this->mTitle->invalidateCache();
1207
1208 // Clear file cache and send purge after above page_touched update was committed
1209 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
1210 $hcu->purgeTitleUrls( $this->mTitle, $hcu::PURGE_PRESEND );
1211
1212 if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI ) {
1213 MediaWikiServices::getInstance()->getMessageCache()
1214 ->updateMessageOverride( $this->mTitle, $this->getContent() );
1215 }
1216 InfoAction::invalidateCache( $this->mTitle, $this->getLatest() );
1217
1218 return true;
1219 }
1220
1239 public function insertOn( $dbw, $pageId = null ) {
1240 $pageIdForInsert = $pageId ? [ 'page_id' => $pageId ] : [];
1241 $row = [
1242 'page_namespace' => $this->mTitle->getNamespace(),
1243 'page_title' => $this->mTitle->getDBkey(),
1244 'page_is_redirect' => 0, // Will set this shortly...
1245 'page_is_new' => 1,
1246 'page_random' => wfRandom(),
1247 'page_touched' => $dbw->timestamp(),
1248 'page_latest' => 0, // Fill this in shortly...
1249 'page_len' => 0, // Fill this in shortly...
1250 ] + $pageIdForInsert;
1251 $dbw->newInsertQueryBuilder()
1252 ->insertInto( 'page' )
1253 ->ignore()
1254 ->row( $row )
1255 ->caller( __METHOD__ )->execute();
1256
1257 if ( $dbw->affectedRows() > 0 ) {
1258 $newid = $pageId ? (int)$pageId : $dbw->insertId();
1259 $this->mId = $newid;
1260 $this->mTitle->resetArticleID( $newid );
1261
1262 // Duplicate the row on secondary links storage if needed but set the page_id
1263 $row['page_id'] = $newid;
1264 $insert = $dbw->newInsertQueryBuilder()
1265 ->insertInto( 'page' )
1266 ->ignore()
1267 ->row( $row )
1268 ->caller( __METHOD__ );
1269 MediaWikiServices::getInstance()->getLinkWriteDuplicator()->duplicate( $insert );
1270
1271 return $newid;
1272 } else {
1273 return false; // nothing changed
1274 }
1275 }
1276
1294 public function updateRevisionOn(
1295 $dbw,
1296 RevisionRecord $revision,
1297 $lastRevision = null,
1298 $lastRevIsRedirect = null
1299 ) {
1300 // TODO: move into PageUpdater or PageStore
1301 // NOTE: when doing that, make sure cached fields get reset in doUserEditContent,
1302 // and in the compat stub!
1303
1304 $revId = $revision->getId();
1305 Assert::parameter( $revId > 0, '$revision->getId()', 'must be > 0' );
1306
1307 $content = $revision->getContent( SlotRecord::MAIN );
1308 $len = $content ? $content->getSize() : 0;
1309 $rt = $content ? $content->getRedirectTarget() : null;
1310 $isNew = $lastRevision === 0;
1311 $isRedirect = $rt !== null;
1312
1313 $conditions = [ 'page_id' => $this->getId() ];
1314
1315 if ( $lastRevision !== null ) {
1316 // An extra check against threads stepping on each other
1317 $conditions['page_latest'] = $lastRevision;
1318 }
1319
1320 $model = $revision->getMainContentModel();
1321
1322 $row = [ /* SET */
1323 'page_latest' => $revId,
1324 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
1325 'page_is_new' => $isNew ? 1 : 0,
1326 'page_is_redirect' => $isRedirect ? 1 : 0,
1327 'page_len' => $len,
1328 'page_content_model' => $model,
1329 ];
1330
1331 $update = $dbw->newUpdateQueryBuilder()
1332 ->update( 'page' )
1333 ->set( $row )
1334 ->where( $conditions )
1335 ->caller( __METHOD__ );
1336 $update->execute();
1337 MediaWikiServices::getInstance()->getLinkWriteDuplicator()->duplicate( $update );
1338
1339 $result = $dbw->affectedRows() > 0;
1340 if ( $result ) {
1341 $insertedRow = $this->pageData( $dbw, [ 'page_id' => $this->getId() ] );
1342
1343 if ( !$insertedRow ) {
1344 throw new RuntimeException( 'Failed to load freshly inserted row' );
1345 }
1346
1347 $this->mTitle->loadFromRow( $insertedRow );
1348 MediaWikiServices::getInstance()->getRedirectStore()
1349 ->updateRedirectTarget( $this, $rt, $lastRevIsRedirect );
1350 $this->setLastEdit( $revision );
1351 $this->mPageIsRedirectField = (bool)$rt;
1352 $this->mIsNew = $isNew;
1353
1354 // Update the LinkCache.
1355 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1356 $linkCache->addGoodLinkObjFromRow(
1357 $this->mTitle,
1358 $insertedRow
1359 );
1360 }
1361
1362 return $result;
1363 }
1364
1378 $aSlots = $a->getSlots();
1379 $bSlots = $b->getSlots();
1380 $changedRoles = $aSlots->getRolesWithDifferentContent( $bSlots );
1381
1382 return ( $changedRoles !== [ SlotRecord::MAIN ] && $changedRoles !== [] );
1383 }
1384
1395 public function supportsSections() {
1396 return $this->getContentHandler()->supportsSections();
1397 }
1398
1412 public function replaceSectionContent(
1413 $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
1414 ) {
1415 $baseRevId = null;
1416 if ( $edittime && $sectionId !== 'new' ) {
1417 $lb = $this->getDBLoadBalancer();
1418 $rev = $this->getRevisionStore()->getRevisionByTimestamp( $this->mTitle, $edittime );
1419 // Try the primary database if this thread may have just added it.
1420 // The logic to fallback to the primary database if the replica is missing
1421 // the revision could be generalized into RevisionStore, but we don't want
1422 // to encourage loading of revisions by timestamp.
1423 if ( !$rev
1424 && $lb->hasReplicaServers()
1425 && $lb->hasOrMadeRecentPrimaryChanges()
1426 ) {
1427 $rev = $this->getRevisionStore()->getRevisionByTimestamp(
1428 $this->mTitle, $edittime, IDBAccessObject::READ_LATEST );
1429 }
1430 if ( $rev ) {
1431 $baseRevId = $rev->getId();
1432 }
1433 }
1434
1435 return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
1436 }
1437
1450 public function replaceSectionAtRev( $sectionId, Content $sectionContent,
1451 $sectionTitle = '', $baseRevId = null
1452 ) {
1453 if ( strval( $sectionId ) === '' ) {
1454 // Whole-page edit; let the whole text through
1455 $newContent = $sectionContent;
1456 } else {
1457 if ( !$this->supportsSections() ) {
1458 throw new BadMethodCallException( "sections not supported for content model " .
1459 $this->getContentHandler()->getModelID() );
1460 }
1461
1462 // T32711: always use current version when adding a new section
1463 if ( $baseRevId === null || $sectionId === 'new' ) {
1464 $oldContent = $this->getContent();
1465 } else {
1466 $revRecord = $this->getRevisionStore()->getRevisionById( $baseRevId );
1467 if ( !$revRecord ) {
1468 wfDebug( __METHOD__ . " asked for bogus section (page: " .
1469 $this->getId() . "; section: $sectionId)" );
1470 return null;
1471 }
1472
1473 $oldContent = $revRecord->getContent( SlotRecord::MAIN );
1474 }
1475
1476 if ( !$oldContent ) {
1477 wfDebug( __METHOD__ . ": no page text" );
1478 return null;
1479 }
1480
1481 $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
1482 }
1483
1484 return $newContent;
1485 }
1486
1496 public function checkFlags( $flags ) {
1497 if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
1498 if ( $this->exists() ) {
1499 $flags |= EDIT_UPDATE;
1500 } else {
1501 $flags |= EDIT_NEW;
1502 }
1503 }
1504
1505 return $flags;
1506 }
1507
1535 private function getDerivedDataUpdater(
1536 ?UserIdentity $forUser = null,
1537 ?RevisionRecord $forRevision = null,
1538 ?RevisionSlotsUpdate $forUpdate = null,
1539 $forEdit = false
1540 ) {
1541 if ( !$forRevision && !$forUpdate ) {
1542 // NOTE: can't re-use an existing derivedDataUpdater if we don't know what the caller is
1543 // going to use it with.
1544 $this->derivedDataUpdater = null;
1545 }
1546
1547 if ( $this->derivedDataUpdater && !$this->derivedDataUpdater->isContentPrepared() ) {
1548 // NOTE: can't re-use an existing derivedDataUpdater if other code that has a reference
1549 // to it did not yet initialize it, because we don't know what data it will be
1550 // initialized with.
1551 $this->derivedDataUpdater = null;
1552 }
1553
1554 // XXX: It would be nice to have an LRU cache instead of trying to re-use a single instance.
1555 // However, there is no good way to construct a cache key. We'd need to check against all
1556 // cached instances.
1557
1558 if ( $this->derivedDataUpdater
1559 && !$this->derivedDataUpdater->isReusableFor(
1560 $forUser,
1561 $forRevision,
1562 $forUpdate,
1563 $forEdit ? $this->getLatest() : null
1564 )
1565 ) {
1566 $this->derivedDataUpdater = null;
1567 }
1568
1569 if ( !$this->derivedDataUpdater ) {
1570 $this->derivedDataUpdater =
1571 $this->getPageUpdaterFactory()->newDerivedPageDataUpdater( $this );
1572 }
1573
1574 return $this->derivedDataUpdater;
1575 }
1576
1624 public function doUserEditContent(
1625 Content $content,
1626 Authority $performer,
1627 $summary,
1628 $flags = 0,
1629 $originalRevId = false,
1630 $tags = [],
1631 $undidRevId = 0
1632 ): PageUpdateStatus {
1633 $useNPPatrol = MediaWikiServices::getInstance()->getMainConfig()->get(
1634 MainConfigNames::UseNPPatrol );
1635 $useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get(
1636 MainConfigNames::UseRCPatrol );
1637 if ( !( $summary instanceof CommentStoreComment ) ) {
1638 $summary = CommentStoreComment::newUnsavedComment( trim( $summary ) );
1639 }
1640
1641 // TODO: this check is here for backwards-compatibility with 1.31 behavior.
1642 // Checking the minoredit right should be done in the same place the 'bot' right is
1643 // checked for the EDIT_FORCE_BOT flag, which is currently in EditPage::attemptSave.
1644 if ( ( $flags & EDIT_MINOR ) && !$performer->isAllowed( 'minoredit' ) ) {
1645 $flags &= ~EDIT_MINOR;
1646 }
1647
1648 $slotsUpdate = new RevisionSlotsUpdate();
1649 $slotsUpdate->modifyContent( SlotRecord::MAIN, $content );
1650
1651 // NOTE: while doUserEditContent() executes, callbacks to getDerivedDataUpdater and
1652 // prepareContentForEdit will generally use the DerivedPageDataUpdater that is also
1653 // used by this PageUpdater. However, there is no guarantee for this.
1654 $updater = $this->newPageUpdater( $performer, $slotsUpdate )
1655 ->setContent( SlotRecord::MAIN, $content )
1656 ->setOriginalRevisionId( $originalRevId );
1657 if ( $undidRevId ) {
1658 $updater->setCause( PageUpdateCauses::CAUSE_UNDO );
1659 $updater->markAsRevert(
1660 EditResult::REVERT_UNDO,
1661 $undidRevId,
1662 $originalRevId ?: null
1663 );
1664 }
1665
1666 $needsPatrol = $useRCPatrol || ( $useNPPatrol && !$this->exists() );
1667
1668 // TODO: this logic should not be in the storage layer, it's here for compatibility
1669 // with 1.31 behavior. Applying the 'autopatrol' right should be done in the same
1670 // place the 'bot' right is handled, which is currently in EditPage::attemptSave.
1671
1672 if ( $needsPatrol && $performer->authorizeWrite( 'autopatrol', $this->getTitle() ) ) {
1673 $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
1674 }
1675
1676 $updater->addTags( $tags );
1677
1678 $revRec = $updater->saveRevision(
1679 $summary,
1680 $flags
1681 );
1682
1683 // $revRec will be null if the edit failed, or if no new revision was created because
1684 // the content did not change.
1685 if ( $revRec ) {
1686 // update cached fields
1687 // TODO: this is currently redundant to what is done in updateRevisionOn.
1688 // But updateRevisionOn() should move into PageStore, and then this will be needed.
1689 $this->setLastEdit( $revRec );
1690 }
1691
1692 return $updater->getStatus();
1693 }
1694
1715 public function newPageUpdater( $performer, ?RevisionSlotsUpdate $forUpdate = null ) {
1716 if ( $performer instanceof Authority ) {
1717 // TODO: Deprecate this. But better get rid of this method entirely.
1718 $performer = $performer->getUser();
1719 }
1720
1721 $pageUpdater = $this->getPageUpdaterFactory()->newPageUpdaterForDerivedPageDataUpdater(
1722 $this,
1723 $performer,
1724 $this->getDerivedDataUpdater( $performer, null, $forUpdate, true )
1725 );
1726
1727 return $pageUpdater;
1728 }
1729
1744 public function makeParserOptions( $context ) {
1745 return self::makeParserOptionsFromTitleAndModel(
1746 $this->getTitle(), $this->getContentModel(), $context
1747 );
1748 }
1749
1759 PageReference $pageRef, string $contentModel, $context
1760 ) {
1761 $options = ParserOptions::newCanonical( $context );
1762
1763 $title = Title::newFromPageReference( $pageRef );
1764 if ( $title->isConversionTable() ) {
1765 // @todo ConversionTable should become a separate content model, so
1766 // we don't need special cases like this one, but see T313455.
1767 $options->disableContentConversion();
1768 }
1769
1770 return $options;
1771 }
1772
1793 public function prepareContentForEdit(
1794 Content $content,
1795 ?RevisionRecord $revision,
1796 UserIdentity $user,
1797 $serialFormat = null,
1798 $useStash = true
1799 ) {
1800 $slots = RevisionSlotsUpdate::newFromContent( [ SlotRecord::MAIN => $content ] );
1801 $updater = $this->getDerivedDataUpdater( $user, $revision, $slots );
1802
1803 if ( !$updater->isUpdatePrepared() ) {
1804 $updater->prepareContent( $user, $slots, $useStash );
1805
1806 if ( $revision ) {
1807 $updater->prepareUpdate(
1808 $revision,
1809 [
1810 'causeAction' => 'prepare-edit',
1811 'causeAgent' => $user->getName(),
1812 ]
1813 );
1814 }
1815 }
1816
1817 return $updater->getPreparedEdit();
1818 }
1819
1835 public function doEditUpdates(
1836 RevisionRecord $revisionRecord,
1837 UserIdentity $user,
1838 array $options = []
1839 ) {
1840 wfDeprecated( __METHOD__, '1.32' ); // emitting warnings since 1.44
1841
1842 $options += [
1843 'causeAction' => 'edit-page',
1844 'causeAgent' => $user->getName(),
1845 'emitEvents' => false // prior page state is unknown, can't emit events
1846 ];
1847
1848 $updater = $this->getDerivedDataUpdater( $user, $revisionRecord );
1849
1850 $updater->prepareUpdate( $revisionRecord, $options );
1851
1852 $updater->doUpdates();
1853 }
1854
1867 public function updateParserCache( array $options = [] ) {
1868 $revision = $this->getRevisionRecord();
1869 if ( !$revision || !$revision->getId() ) {
1870 LoggerFactory::getInstance( 'wikipage' )->info(
1871 __METHOD__ . ' called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
1872 );
1873 return;
1874 }
1875 $userIdentity = $revision->getUser( RevisionRecord::RAW );
1876
1877 $updater = $this->getDerivedDataUpdater( $userIdentity, $revision );
1878 $updater->prepareUpdate( $revision, $options );
1879 $updater->doParserCacheUpdate();
1880 }
1881
1910 public function doSecondaryDataUpdates( array $options = [] ) {
1911 $options['recursive'] ??= true;
1912 $revision = $this->getRevisionRecord();
1913 if ( !$revision || !$revision->getId() ) {
1914 LoggerFactory::getInstance( 'wikipage' )->info(
1915 __METHOD__ . ' called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
1916 );
1917 return;
1918 }
1919 $userIdentity = $revision->getUser( RevisionRecord::RAW );
1920
1921 $updater = $this->getDerivedDataUpdater( $userIdentity, $revision );
1922 $updater->prepareUpdate( $revision, $options );
1923 $updater->doSecondaryDataUpdates( $options );
1924 }
1925
1940 public function doUpdateRestrictions( array $limit, array $expiry,
1941 &$cascade, $reason, UserIdentity $user, $tags = []
1942 ) {
1943 $services = MediaWikiServices::getInstance();
1944 $readOnlyMode = $services->getReadOnlyMode();
1945 if ( $readOnlyMode->isReadOnly() ) {
1946 return Status::newFatal( 'readonlytext', $readOnlyMode->getReason() );
1947 }
1948
1949 $this->loadPageData( IDBAccessObject::READ_LATEST );
1950 $restrictionStore = $services->getRestrictionStore();
1951 $restrictionStore->loadRestrictions( $this->mTitle, IDBAccessObject::READ_LATEST );
1952 $restrictionTypes = $restrictionStore->listApplicableRestrictionTypes( $this->mTitle );
1953 $id = $this->getId();
1954
1955 if ( !$cascade ) {
1956 $cascade = false;
1957 }
1958
1959 // Take this opportunity to purge out expired restrictions
1960 Title::purgeExpiredRestrictions();
1961
1962 // @todo: Same limitations as described in ProtectionForm.php (line 37);
1963 // we expect a single selection, but the schema allows otherwise.
1964 $isProtected = false;
1965 $protect = false;
1966 $changed = false;
1967
1968 $dbw = $services->getConnectionProvider()->getPrimaryDatabase();
1969 $restrictionMapBefore = [];
1970 $restrictionMapAfter = [];
1971
1972 foreach ( $restrictionTypes as $action ) {
1973 if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) {
1974 $expiry[$action] = 'infinity';
1975 }
1976
1977 // Get current restrictions on $action
1978 $restrictionMapBefore[$action] = $restrictionStore->getRestrictions( $this->mTitle, $action );
1979 $limit[$action] ??= '';
1980
1981 if ( $limit[$action] === '' ) {
1982 $restrictionMapAfter[$action] = [];
1983 } else {
1984 $protect = true;
1985 $restrictionMapAfter[$action] = explode( ',', $limit[$action] );
1986 }
1987
1988 $current = implode( ',', $restrictionMapBefore[$action] );
1989 if ( $current != '' ) {
1990 $isProtected = true;
1991 }
1992
1993 if ( $limit[$action] != $current ) {
1994 $changed = true;
1995 } elseif ( $limit[$action] != '' ) {
1996 // Only check expiry change if the action is actually being
1997 // protected, since expiry does nothing on an not-protected
1998 // action.
1999 if ( $restrictionStore->getRestrictionExpiry( $this->mTitle, $action ) != $expiry[$action] ) {
2000 $changed = true;
2001 }
2002 }
2003 }
2004
2005 if ( !$changed && $protect && $restrictionStore->areRestrictionsCascading( $this->mTitle ) != $cascade ) {
2006 $changed = true;
2007 }
2008
2009 // If nothing has changed, do nothing
2010 if ( !$changed ) {
2011 return Status::newGood();
2012 }
2013
2014 if ( !$protect ) { // No protection at all means unprotection
2015 $revCommentMsg = 'unprotectedarticle-comment';
2016 $logAction = 'unprotect';
2017 } elseif ( $isProtected ) {
2018 $revCommentMsg = 'modifiedarticleprotection-comment';
2019 $logAction = 'modify';
2020 } else {
2021 $revCommentMsg = 'protectedarticle-comment';
2022 $logAction = 'protect';
2023 }
2024
2025 $logRelationsValues = [];
2026 $logRelationsField = null;
2027 $logParamsDetails = [];
2028
2029 // Null revision (used for change tag insertion)
2030 $dummyRevisionRecord = null;
2031
2032 $legacyUser = $services->getUserFactory()->newFromUserIdentity( $user );
2033 if ( !$this->getHookRunner()->onArticleProtect( $this, $legacyUser, $limit, $reason ) ) {
2034 return Status::newGood();
2035 }
2036
2037 if ( $id ) { // Protection of existing page
2038 // Only certain restrictions can cascade...
2039 $editrestriction = isset( $limit['edit'] )
2040 ? [ $limit['edit'] ]
2041 : $restrictionStore->getRestrictions( $this->mTitle, 'edit' );
2042 foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
2043 $editrestriction[$key] = 'editprotected'; // backwards compatibility
2044 }
2045 foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
2046 $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
2047 }
2048
2049 $cascadingRestrictionLevels = $services->getMainConfig()
2050 ->get( MainConfigNames::CascadingRestrictionLevels );
2051
2052 foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
2053 $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
2054 }
2055 foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
2056 $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
2057 }
2058
2059 // The schema allows multiple restrictions
2060 if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
2061 $cascade = false;
2062 }
2063
2064 // insert dummy revision to identify the page protection change as edit summary
2065 $dummyRevisionRecord = $this->insertNullProtectionRevision(
2066 $revCommentMsg,
2067 $limit,
2068 $expiry,
2069 $cascade,
2070 $reason,
2071 $user
2072 );
2073
2074 if ( $dummyRevisionRecord === null ) {
2075 return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
2076 }
2077
2078 $logRelationsField = 'pr_id';
2079
2080 // T214035: Avoid deadlock on MySQL.
2081 // Do a DELETE by primary key (pr_id) for any existing protection rows.
2082 // On MySQL and derivatives, unconditionally deleting by page ID (pr_page) would.
2083 // place a gap lock if there are no matching rows. This can deadlock when another
2084 // thread modifies protection settings for page IDs in the same gap.
2085 $existingProtectionIds = $dbw->newSelectQueryBuilder()
2086 ->select( 'pr_id' )
2087 ->from( 'page_restrictions' )
2088 ->where( [ 'pr_page' => $id, 'pr_type' => array_map( 'strval', array_keys( $limit ) ) ] )
2089 ->caller( __METHOD__ )->fetchFieldValues();
2090
2091 if ( $existingProtectionIds ) {
2092 $dbw->newDeleteQueryBuilder()
2093 ->deleteFrom( 'page_restrictions' )
2094 ->where( [ 'pr_id' => $existingProtectionIds ] )
2095 ->caller( __METHOD__ )->execute();
2096 }
2097
2098 // Update restrictions table
2099 foreach ( $limit as $action => $restrictions ) {
2100 if ( $restrictions != '' ) {
2101 $cascadeValue = ( $cascade && $action == 'edit' ) ? 1 : 0;
2102 $dbw->newInsertQueryBuilder()
2103 ->insertInto( 'page_restrictions' )
2104 ->row( [
2105 'pr_page' => $id,
2106 'pr_type' => $action,
2107 'pr_level' => $restrictions,
2108 'pr_cascade' => $cascadeValue,
2109 'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
2110 ] )
2111 ->caller( __METHOD__ )->execute();
2112 $logRelationsValues[] = $dbw->insertId();
2113 $logParamsDetails[] = [
2114 'type' => $action,
2115 'level' => $restrictions,
2116 'expiry' => $expiry[$action],
2117 'cascade' => (bool)$cascadeValue,
2118 ];
2119 }
2120 }
2121 } else { // Protection of non-existing page (also known as "title protection")
2122 // Cascade protection is meaningless in this case
2123 $cascade = false;
2124
2125 if ( $limit['create'] != '' ) {
2126 $commentFields = $services->getCommentStore()->insert( $dbw, 'pt_reason', $reason );
2127 $dbw->newReplaceQueryBuilder()
2128 ->table( 'protected_titles' )
2129 ->uniqueIndexFields( [ 'pt_namespace', 'pt_title' ] )
2130 ->rows( [
2131 'pt_namespace' => $this->mTitle->getNamespace(),
2132 'pt_title' => $this->mTitle->getDBkey(),
2133 'pt_create_perm' => $limit['create'],
2134 'pt_timestamp' => $dbw->timestamp(),
2135 'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
2136 'pt_user' => $user->getId(),
2137 ] + $commentFields )
2138 ->caller( __METHOD__ )->execute();
2139 $logParamsDetails[] = [
2140 'type' => 'create',
2141 'level' => $limit['create'],
2142 'expiry' => $expiry['create'],
2143 ];
2144 } else {
2145 $dbw->newDeleteQueryBuilder()
2146 ->deleteFrom( 'protected_titles' )
2147 ->where( [
2148 'pt_namespace' => $this->mTitle->getNamespace(),
2149 'pt_title' => $this->mTitle->getDBkey()
2150 ] )
2151 ->caller( __METHOD__ )->execute();
2152 }
2153 }
2154
2155 $this->getHookRunner()->onArticleProtectComplete( $this, $legacyUser, $limit, $reason );
2156
2157 $restrictionStore->flushRestrictions( $this->mTitle );
2158
2159 InfoAction::invalidateCache( $this->mTitle );
2160
2161 if ( $logAction == 'unprotect' ) {
2162 $params = [];
2163 } else {
2164 $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
2165 $params = [
2166 '4::description' => $protectDescriptionLog, // parameter for IRC
2167 '5:bool:cascade' => $cascade,
2168 'details' => $logParamsDetails, // parameter for localize and api
2169 ];
2170 }
2171
2172 // Update the protection log
2173 $logEntry = new ManualLogEntry( 'protect', $logAction );
2174 $logEntry->setTarget( $this->mTitle );
2175 $logEntry->setComment( $reason );
2176 $logEntry->setPerformer( $user );
2177 $logEntry->setParameters( $params );
2178 if ( $dummyRevisionRecord !== null ) {
2179 $logEntry->setAssociatedRevId( $dummyRevisionRecord->getId() );
2180 }
2181 $logEntry->addTags( $tags );
2182 if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
2183 $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
2184 }
2185 $logId = $logEntry->insert();
2186 $logEntry->publish( $logId );
2187
2188 $event = new PageProtectionChangedEvent(
2189 $this,
2190 $restrictionMapBefore,
2191 $restrictionMapAfter,
2192 $expiry,
2193 $cascade,
2194 $user,
2195 $reason,
2196 $tags
2197 );
2198
2199 $dispatcher = MediaWikiServices::getInstance()->getDomainEventDispatcher();
2200 $dispatcher->dispatch( $event, $services->getConnectionProvider() );
2201
2202 return Status::newGood( $logId );
2203 }
2204
2226 Assert::precondition(
2227 $this->derivedDataUpdater !== null,
2228 'There is no ongoing update tracked by this instance of WikiPage!'
2229 );
2230
2231 return $this->derivedDataUpdater;
2232 }
2233
2249 string $revCommentMsg,
2250 array $limit,
2251 array $expiry,
2252 bool $cascade,
2253 string $reason,
2254 UserIdentity $user
2255 ): ?RevisionRecord {
2256 // Prepare a dummy revision to be added to the history
2257 $editComment = wfMessage(
2258 $revCommentMsg,
2259 $this->mTitle->getPrefixedText(),
2260 $user->getName()
2261 )->inContentLanguage()->text();
2262 if ( $reason ) {
2263 $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
2264 }
2265 $protectDescription = $this->protectDescription( $limit, $expiry );
2266 if ( $protectDescription ) {
2267 $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2268 $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )
2269 ->inContentLanguage()->text();
2270 }
2271 if ( $cascade ) {
2272 $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2273 $editComment .= wfMessage( 'brackets' )->params(
2274 wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
2275 )->inContentLanguage()->text();
2276 }
2277
2278 return $this->newPageUpdater( $user )
2279 ->setCause( PageUpdater::CAUSE_PROTECTION_CHANGE )
2280 ->saveDummyRevision( $editComment, EDIT_SILENT | EDIT_MINOR );
2281 }
2282
2287 protected function formatExpiry( $expiry ) {
2288 if ( $expiry != 'infinity' ) {
2289 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2290 return wfMessage(
2291 'protect-expiring',
2292 $contLang->timeanddate( $expiry, false, false ),
2293 $contLang->date( $expiry, false, false ),
2294 $contLang->time( $expiry, false, false )
2295 )->inContentLanguage()->text();
2296 } else {
2297 return wfMessage( 'protect-expiry-indefinite' )
2298 ->inContentLanguage()->text();
2299 }
2300 }
2301
2309 public function protectDescription( array $limit, array $expiry ) {
2310 $protectDescription = '';
2311
2312 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2313 # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
2314 # All possible message keys are listed here for easier grepping:
2315 # * restriction-create
2316 # * restriction-edit
2317 # * restriction-move
2318 # * restriction-upload
2319 $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
2320 # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
2321 # with '' filtered out. All possible message keys are listed below:
2322 # * protect-level-autoconfirmed
2323 # * protect-level-sysop
2324 $restrictionsText = wfMessage( 'protect-level-' . $restrictions )
2325 ->inContentLanguage()->text();
2326
2327 $expiryText = $this->formatExpiry( $expiry[$action] );
2328
2329 if ( $protectDescription !== '' ) {
2330 $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2331 }
2332 $protectDescription .= wfMessage( 'protect-summary-desc' )
2333 ->params( $actionText, $restrictionsText, $expiryText )
2334 ->inContentLanguage()->text();
2335 }
2336
2337 return $protectDescription;
2338 }
2339
2351 public function protectDescriptionLog( array $limit, array $expiry ) {
2352 $protectDescriptionLog = '';
2353
2354 $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark();
2355 foreach ( array_filter( $limit ) as $action => $restrictions ) {
2356 $expiryText = $this->formatExpiry( $expiry[$action] );
2357 $protectDescriptionLog .=
2358 $dirMark .
2359 "[$action=$restrictions] ($expiryText)";
2360 }
2361
2362 return trim( $protectDescriptionLog );
2363 }
2364
2379 public function isBatchedDelete( $safetyMargin = 0 ) {
2380 $deleteRevisionsBatchSize = MediaWikiServices::getInstance()
2381 ->getMainConfig()->get( MainConfigNames::DeleteRevisionsBatchSize );
2382
2383 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
2384 $revCount = $this->getRevisionStore()->countRevisionsByPageId( $dbr, $this->getId() );
2385 $revCount += $safetyMargin;
2386
2387 return $revCount >= $deleteRevisionsBatchSize;
2388 }
2389
2418 public function doDeleteArticleReal(
2419 $reason, UserIdentity $deleter, $suppress = false, $u1 = null, &$error = '', $u2 = null,
2420 $tags = [], $logsubtype = 'delete', $immediate = false
2421 ) {
2422 $services = MediaWikiServices::getInstance();
2423 $deletePage = $services->getDeletePageFactory()->newDeletePage(
2424 $this,
2425 $services->getUserFactory()->newFromUserIdentity( $deleter )
2426 );
2427
2428 $status = $deletePage
2429 ->setSuppress( $suppress )
2430 ->setTags( $tags ?: [] )
2431 ->setLogSubtype( $logsubtype )
2432 ->forceImmediate( $immediate )
2433 ->keepLegacyHookErrorsSeparate()
2434 ->deleteUnsafe( $reason );
2435 $error = $deletePage->getLegacyHookErrors();
2436 if ( $status->isGood() ) {
2437 // BC with old return format
2438 if ( $deletePage->deletionsWereScheduled()[DeletePage::PAGE_BASE] ) {
2439 $status->warning( 'delete-scheduled', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
2440 } else {
2441 // @phan-suppress-next-line PhanTypeMismatchProperty Changing the type of the status parameter
2442 $status->value = $deletePage->getSuccessfulDeletionsIDs()[DeletePage::PAGE_BASE];
2443 }
2444 }
2445 return $status;
2446 }
2447
2454 public function lockAndGetLatest() {
2455 $dbw = $this->getConnectionProvider()->getPrimaryDatabase();
2456 return (int)$dbw->newSelectQueryBuilder()
2457 ->select( 'page_latest' )
2458 ->forUpdate()
2459 ->from( 'page' )
2460 ->where( [
2461 'page_id' => $this->getId(),
2462 // Typically page_id is enough, but some code might try to do
2463 // updates assuming the title is the same, so verify that
2464 'page_namespace' => $this->getTitle()->getNamespace(),
2465 'page_title' => $this->getTitle()->getDBkey()
2466 ] )
2467 ->caller( __METHOD__ )->fetchField();
2468 }
2469
2483 public static function onArticleCreate( Title $title, $maybeIsRedirect = true ) {
2484 // TODO: move this into a PageEventEmitter service
2485
2486 // Update existence markers on article/talk tabs...
2487 $other = $title->getOtherPage();
2488
2489 $services = MediaWikiServices::getInstance();
2490 $hcu = $services->getHTMLCacheUpdater();
2491 $hcu->purgeTitleUrls( [ $title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2492
2493 $title->touchLinks();
2494 $services->getRestrictionStore()->deleteCreateProtection( $title );
2495
2496 $services->getLinkCache()->invalidateTitle( $title );
2497
2498 DeferredUpdates::addCallableUpdate(
2499 static function () use ( $title, $maybeIsRedirect ) {
2500 self::queueBacklinksJobs( $title, true, $maybeIsRedirect, 'create-page' );
2501 }
2502 );
2503
2504 if ( $title->getNamespace() === NS_CATEGORY ) {
2505 // Load the Category object, which will schedule a job to create
2506 // the category table row if necessary. Checking a replica DB is ok
2507 // here, in the worst case it'll run an unnecessary recount job on
2508 // a category that probably doesn't have many members.
2509 Category::newFromTitle( $title )->getID();
2510 }
2511 }
2512
2521 public static function onArticleDelete( Title $title ) {
2522 // TODO: move this into a PageEventEmitter service
2523
2524 // Update existence markers on article/talk tabs...
2525 $other = $title->getOtherPage();
2526
2527 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2528 $hcu->purgeTitleUrls( [ $title, $other ], $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2529
2530 $title->touchLinks();
2531
2532 $services = MediaWikiServices::getInstance();
2533 $services->getLinkCache()->invalidateTitle( $title );
2534
2535 InfoAction::invalidateCache( $title );
2536
2537 // Invalidate caches of articles which include this page
2538 DeferredUpdates::addCallableUpdate( static function () use ( $title ) {
2539 self::queueBacklinksJobs( $title, true, true, 'delete-page' );
2540 } );
2541
2542 // TODO: Move to ChangeTrackingEventIngress when ready,
2543 // but make sure it happens on deletions and page moves by adding
2544 // the appropriate assertions to ChangeTrackingEventIngressSpyTrait.
2545 // Messages
2546 // User talk pages
2547 if ( $title->getNamespace() === NS_USER_TALK ) {
2548 $user = User::newFromName( $title->getText(), false );
2549 if ( $user ) {
2550 MediaWikiServices::getInstance()
2551 ->getTalkPageNotificationManager()
2552 ->removeUserHasNewMessages( $user );
2553 }
2554 }
2555
2556 // TODO: Create MediaEventIngress and move this there.
2557 // Image redirects
2558 $services->getRepoGroup()->getLocalRepo()->invalidateImageRedirect( $title );
2559
2560 // Purge cross-wiki cache entities referencing this page
2561 self::purgeInterwikiCheckKey( $title );
2562 }
2563
2574 public static function onArticleEdit(
2575 Title $title,
2576 ?RevisionRecord $revRecord = null,
2577 $slotsChanged = null,
2578 $maybeRedirectChanged = true
2579 ) {
2580 // TODO: move this into a PageEventEmitter service
2581
2582 DeferredUpdates::addCallableUpdate(
2583 static function () use ( $title, $slotsChanged, $maybeRedirectChanged ) {
2584 self::queueBacklinksJobs(
2585 $title,
2586 $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ),
2587 $maybeRedirectChanged,
2588 'edit-page'
2589 );
2590 }
2591 );
2592
2593 $services = MediaWikiServices::getInstance();
2594 $services->getLinkCache()->invalidateTitle( $title );
2595
2596 $hcu = MediaWikiServices::getInstance()->getHTMLCacheUpdater();
2597 $hcu->purgeTitleUrls( $title, $hcu::PURGE_INTENT_TXROUND_REFLECTED );
2598
2599 // Purge ?action=info cache
2600 $revid = $revRecord ? $revRecord->getId() : null;
2601 DeferredUpdates::addCallableUpdate( static function () use ( $title, $revid ) {
2602 InfoAction::invalidateCache( $title, $revid );
2603 } );
2604
2605 // Purge cross-wiki cache entities referencing this page
2606 self::purgeInterwikiCheckKey( $title );
2607 }
2608
2609 private static function queueBacklinksJobs(
2610 Title $title, bool $mainSlotChanged, bool $maybeRedirectChanged, string $causeAction
2611 ) {
2612 $services = MediaWikiServices::getInstance();
2613 $backlinkCache = $services->getBacklinkCacheFactory()->getBacklinkCache( $title );
2614
2615 $jobs = [];
2616 if ( $mainSlotChanged
2617 && $backlinkCache->hasLinks( 'templatelinks' )
2618 ) {
2619 // Invalidate caches of articles which include this page.
2620 // Only for the main slot, because only the main slot is transcluded.
2621 // TODO: MCR: not true for TemplateStyles! [SlotHandler]
2622 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2623 $title,
2624 'templatelinks',
2625 [ 'causeAction' => $causeAction ]
2626 );
2627 }
2628 // Images
2629 if ( $maybeRedirectChanged && $title->getNamespace() === NS_FILE
2630 && $backlinkCache->hasLinks( 'imagelinks' )
2631 ) {
2632 // Process imagelinks in case the redirect target has changed
2633 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2634 $title,
2635 'imagelinks',
2636 [ 'causeAction' => $causeAction ]
2637 );
2638 }
2639 // Invalidate the caches of all pages which redirect here
2640 if ( $backlinkCache->hasLinks( 'redirect' ) ) {
2641 $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
2642 $title,
2643 'redirect',
2644 [ 'causeAction' => $causeAction ]
2645 );
2646 }
2647 if ( $jobs ) {
2648 $services->getJobQueueGroup()->push( $jobs );
2649 }
2650 }
2651
2655 private static function purgeInterwikiCheckKey( Title $title ) {
2656 $enableScaryTranscluding = MediaWikiServices::getInstance()->getMainConfig()->get(
2657 MainConfigNames::EnableScaryTranscluding );
2658
2659 if ( !$enableScaryTranscluding ) {
2660 return; // @todo: perhaps this wiki is only used as a *source* for content?
2661 }
2662
2663 DeferredUpdates::addCallableUpdate( static function () use ( $title ) {
2664 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2665 $cache->resetCheckKey(
2666 // Do not include the namespace since there can be multiple aliases to it
2667 // due to different namespace text definitions on different wikis. This only
2668 // means that some cache invalidations happen that are not strictly needed.
2669 $cache->makeGlobalKey(
2670 'interwiki-page',
2671 WikiMap::getCurrentWikiDbDomain()->getId(),
2672 $title->getDBkey()
2673 )
2674 );
2675 } );
2676 }
2677
2684 public function getCategories() {
2685 $services = MediaWikiServices::getInstance();
2686 $id = $this->getId();
2687 if ( $id == 0 ) {
2688 return $services->getTitleFactory()->newTitleArrayFromResult( new FakeResultWrapper( [] ) );
2689 }
2690
2691 $dbr = $services->getConnectionProvider()->getReplicaDatabase( CategoryLinksTable::VIRTUAL_DOMAIN );
2692 $res = $dbr->newSelectQueryBuilder()
2693 ->select( [ 'page_title' => 'lt_title', 'page_namespace' => (string)NS_CATEGORY ] )
2694 ->from( 'categorylinks' )
2695 ->join( 'linktarget', null, [ 'cl_target_id = lt_id', 'lt_namespace = ' . NS_CATEGORY ] )
2696 ->where( [ 'cl_from' => $id ] )
2697 ->caller( __METHOD__ )
2698 ->fetchResultSet();
2699
2700 return $services->getTitleFactory()->newTitleArrayFromResult( $res );
2701 }
2702
2709 public function getHiddenCategories() {
2710 $id = $this->getId();
2711
2712 if ( $id == 0 ) {
2713 return [];
2714 }
2715
2716 $categoryLinksDb = $this->getConnectionProvider()->getReplicaDatabase( CategoryLinksTable::VIRTUAL_DOMAIN );
2717 $categoryTitles = $categoryLinksDb->newSelectQueryBuilder()
2718 ->select( 'lt_title' )
2719 ->from( 'categorylinks' )
2720 ->join( 'linktarget', null, 'cl_target_id = lt_id' )
2721 ->where( [ 'cl_from' => $id, 'lt_namespace' => NS_CATEGORY ] )
2722 ->caller( __METHOD__ )
2723 ->fetchFieldValues();
2724
2725 if ( $categoryTitles === [] ) {
2726 return [];
2727 }
2728
2729 $dbr = $this->getConnectionProvider()->getReplicaDatabase();
2730 $hiddenTitles = $dbr->newSelectQueryBuilder()
2731 ->select( [ 'page_title' ] )
2732 ->from( 'page_props' )
2733 ->join( 'page', null, 'page_id = pp_page' )
2734 ->where( [
2735 'pp_propname' => 'hiddencat',
2736 'page_namespace' => NS_CATEGORY,
2737 'page_title' => $categoryTitles
2738 ] )
2739 ->caller( __METHOD__ )
2740 ->fetchFieldValues();
2741
2742 $result = [];
2743 foreach ( $categoryTitles as $title ) {
2744 if ( in_array( $title, $hiddenTitles ) ) {
2745 $result[] = Title::makeTitle( NS_CATEGORY, $title );
2746 }
2747 }
2748
2749 return $result;
2750 }
2751
2759 public function getAutoDeleteReason( &$hasHistory = false ) {
2760 if ( func_num_args() === 1 ) {
2761 wfDeprecated( __METHOD__ . ': $hasHistory parameter', '1.38' );
2762 return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
2763 }
2764 return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle() );
2765 }
2766
2779 public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2780 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
2781 return;
2782 }
2783
2784 if ( !$this->getHookRunner()->onOpportunisticLinksUpdate( $this,
2785 $this->mTitle, $parserOutput )
2786 ) {
2787 return;
2788 }
2789
2790 $config = MediaWikiServices::getInstance()->getMainConfig();
2791
2792 $params = [
2793 'isOpportunistic' => true,
2794 'rootJobTimestamp' => $parserOutput->getCacheTime()
2795 ];
2796
2797 if ( MediaWikiServices::getInstance()->getRestrictionStore()->areRestrictionsCascading( $this->mTitle ) ) {
2798 // In general, MediaWiki does not re-run LinkUpdate (e.g. for search index, category
2799 // listings, and backlinks for Whatlinkshere), unless either the page was directly
2800 // edited, or was re-generate following a template edit propagating to an affected
2801 // page. As such, during page views when there is no valid ParserCache entry,
2802 // we re-parse and save, but leave indexes as-is.
2803 //
2804 // We make an exception for pages that have cascading protection (perhaps for a wiki's
2805 // "Main Page"). When such page is re-parsed on-demand after a parser cache miss, we
2806 // queue a high-priority LinksUpdate job, to ensure that we really protect all
2807 // content that is currently transcluded onto the page. This is important, because
2808 // wikitext supports conditional statements based on the current time, which enables
2809 // transcluding of a different subpage based on which day it is, and then show that
2810 // information on the Main Page, without the Main Page itself being edited.
2811 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush(
2812 RefreshLinksJob::newPrioritized( $this->mTitle, $params )
2813 );
2814 } elseif (
2815 (
2816 // "Dynamic" content (eg time/random magic words)
2817 !$config->get( MainConfigNames::MiserMode ) &&
2818 $parserOutput->hasReducedExpiry()
2819 )
2820 ||
2821 (
2822 // Asynchronous content
2823 $config->get( MainConfigNames::ParserCacheAsyncRefreshJobs ) &&
2824 $parserOutput->getOutputFlag( ParserOutputFlags::HAS_ASYNC_CONTENT ) &&
2825 !$parserOutput->getOutputFlag( ParserOutputFlags::ASYNC_NOT_READY )
2826 )
2827 ) {
2828 // Assume the output contains "dynamic" time/random based magic words
2829 // or asynchronous content that wasn't "ready" the first time the
2830 // page was parsed.
2831 // Only update pages that expired due to dynamic content and NOT due to edits
2832 // to referenced templates/files. When the cache expires due to dynamic content,
2833 // page_touched is unchanged. We want to avoid triggering redundant jobs due to
2834 // views of pages that were just purged via HTMLCacheUpdateJob. In that case, the
2835 // template/file edit already triggered recursive RefreshLinksJob jobs.
2836 if ( $this->getLinksTimestamp() > $this->getTouched() ) {
2837 // If a page is uncacheable, do not keep spamming a job for it.
2838 // Although it would be de-duplicated, it would still waste I/O.
2839 $services = MediaWikiServices::getInstance()->getObjectCacheFactory();
2840 $cache = $services->getLocalClusterInstance();
2841 $key = $cache->makeKey( 'dynamic-linksupdate', 'last', $this->getId() );
2842 $ttl = max( $parserOutput->getCacheExpiry(), 3600 );
2843 if ( $cache->add( $key, time(), $ttl ) ) {
2844 MediaWikiServices::getInstance()->getJobQueueGroup()->lazyPush(
2845 RefreshLinksJob::newDynamic( $this->mTitle, $params )
2846 );
2847 }
2848 }
2849 }
2850 }
2851
2859 public function isLocal() {
2860 return true;
2861 }
2862
2872 public function getWikiDisplayName() {
2873 $sitename = MediaWikiServices::getInstance()->getMainConfig()->get(
2874 MainConfigNames::Sitename );
2875 return $sitename;
2876 }
2877
2886 public function getSourceURL() {
2887 return $this->getTitle()->getCanonicalURL();
2888 }
2889
2896 public function __wakeup() {
2897 // Make sure we re-fetch the latest state from the database.
2898 // In particular, the latest revision may have changed.
2899 // As a side-effect, this makes sure mLastRevision doesn't
2900 // end up being an instance of the old Revision class (see T259181),
2901 // especially since that class was removed entirely in 1.37.
2902 $this->clear();
2903 }
2904
2909 public function getNamespace(): int {
2910 return $this->getTitle()->getNamespace();
2911 }
2912
2917 public function getDBkey(): string {
2918 return $this->getTitle()->getDBkey();
2919 }
2920
2925 public function getWikiId() {
2926 return $this->getTitle()->getWikiId();
2927 }
2928
2933 public function canExist(): bool {
2934 return true;
2935 }
2936
2941 public function __toString(): string {
2942 return $this->mTitle->__toString();
2943 }
2944
2949 public function isSamePageAs( PageReference $other ): bool {
2950 // NOTE: keep in sync with PageReferenceValue::isSamePageAs()!
2951 return $this->getWikiId() === $other->getWikiId()
2952 && $this->getNamespace() === $other->getNamespace()
2953 && $this->getDBkey() === $other->getDBkey();
2954 }
2955
2968 // TODO: replace individual member fields with a PageRecord instance that is always present
2969
2970 if ( !$this->mDataLoaded ) {
2971 $this->loadPageData();
2972 }
2973
2974 Assert::precondition(
2975 $this->exists(),
2976 'This WikiPage instance does not represent an existing page: ' . $this->mTitle
2977 );
2978
2979 return new PageStoreRecord(
2980 (object)[
2981 'page_id' => $this->getId(),
2982 'page_namespace' => $this->mTitle->getNamespace(),
2983 'page_title' => $this->mTitle->getDBkey(),
2984 'page_latest' => $this->mLatest,
2985 'page_is_new' => $this->mIsNew ? 1 : 0,
2986 'page_is_redirect' => $this->mPageIsRedirectField ? 1 : 0,
2987 'page_touched' => $this->getTouched(),
2988 'page_lang' => $this->getLanguage()
2989 ],
2990 PageIdentity::LOCAL
2991 );
2992 }
2993
2997 private function getConnectionProvider(): \Wikimedia\Rdbms\IConnectionProvider {
2998 return MediaWikiServices::getInstance()->getConnectionProvider();
2999 }
3000
3001}
3002
3004class_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:69
Displays information about a page.
Category objects are immutable, strictly speaking.
Definition Category.php:29
Value object for a comment stored by CommentStore.
Base class for content handling.
Defer callable updates to run later in the PHP process.
Represents information returned by WikiPage::prepareContentForEdit()
Job to purge the HTML/file cache for all pages that link to or use another page or file.
Job to update link tables for rerendered wiki pages.
Create PSR-3 logger objects.
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
getMainConfig()
Returns the Config object that provides configuration for MediaWiki core.
static getInstance()
Returns the global default instance of the top level service locator.
Domain event representing changes to page protection (aka restriction levels).
Immutable data record representing an editable page on a wiki.
Base representation for an editable wiki page.
Definition WikiPage.php:82
int $mDataLoadedFrom
One of the READ_* constants.
Definition WikiPage.php:132
getMinorEdit()
Returns true if last revision was marked as "minor edit".
Definition WikiPage.php:890
getUser( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Definition WikiPage.php:814
loadLastEdit()
Loads everything except the text This isn't necessary for all uses, so it's only done if needed.
Definition WikiPage.php:707
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:489
getParserOutput(?ParserOptions $parserOptions=null, $oldid=null, $noCache=false)
Get a ParserOutput for the given ParserOptions and revision ID.
getContentHandler()
Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
Definition WikiPage.php:241
updateRevisionOn( $dbw, RevisionRecord $revision, $lastRevision=null, $lastRevIsRedirect=null)
Update the page record to point to a newly saved revision.
getContent( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Get the content of the latest revision.
Definition WikiPage.php:775
clear()
Clear the object.
Definition WikiPage.php:258
__construct(PageIdentity $pageIdentity)
Definition WikiPage.php:164
getRevisionRecord()
Get the latest revision.
Definition WikiPage.php:757
static makeParserOptionsFromTitleAndModel(PageReference $pageRef, string $contentModel, $context)
Create canonical parser options for a given title and content model.
__toString()
Returns an informative human readable unique representation of the page identity, for use as a cache ...
getWikiDisplayName()
The display name for the site this content come from.
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, UserIdentity $user, $tags=[])
Update the article's restriction field, and leave a log entry.
static onArticleEdit(Title $title, ?RevisionRecord $revRecord=null, $slotsChanged=null, $maybeRedirectChanged=true)
Purge caches on page update etc.
int false $mLatest
False means "not loaded".
Definition WikiPage.php:116
string $mTimestamp
Timestamp of the latest revision or empty string if not loaded.
Definition WikiPage.php:142
getLatest( $wikiId=self::LOCAL)
Get the page_latest field.
Definition WikiPage.php:694
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:577
pageDataFromId( $dbr, $id, $options=[])
Fetch a page record matching the requested ID.
Definition WikiPage.php:394
insertOn( $dbw, $pageId=null)
Insert a new empty page record for this article.
supportsSections()
Returns true if this page's content model supports sections.
pageData( $dbr, $conditions, $options=[])
Fetch a page record with the given conditions.
Definition WikiPage.php:343
clearPreparedEdit()
Clear the mPreparedEdit cache field, as may be needed by mutable content types.
Definition WikiPage.php:290
getTitle()
Get the title object of the article.
Definition WikiPage.php:250
getRedirectTarget()
If this page is a redirect, get its target.
Definition WikiPage.php:976
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:180
isNew()
Tests if the page is new (only has one revision).
Definition WikiPage.php:595
followRedirect()
Get the Title object or URL this page redirects to.
Definition WikiPage.php:998
static convertSelectType( $type)
Convert deprecated 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants.
Definition WikiPage.php:190
static onArticleCreate(Title $title, $maybeIsRedirect=true)
The onArticle*() functions are supposed to be a kind of hooks which should be called whenever any of ...
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Should the parser cache be used?
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
getCreator( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Get the User object of the user who created the page.
Definition WikiPage.php:835
clearCacheFields()
Clear the object cache fields.
Definition WikiPage.php:269
insertRedirectEntry(LinkTarget $rt, $oldLatest=null)
Insert or update the redirect table entry for this page to indicate it redirects to $rt.
Definition WikiPage.php:988
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:660
checkTouched()
Loads page_touched and returns a value indicating if it should be used.
Definition WikiPage.php:652
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:540
getHiddenCategories()
Returns a list of hidden categories this page is a member of.
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Opportunistically enqueue link update jobs after a fresh parser output was generated.
toPageRecord()
Returns the page represented by this WikiPage as a PageStoreRecord.
doEditUpdates(RevisionRecord $revisionRecord, UserIdentity $user, array $options=[])
Do standard deferred updates after page edit.
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
pageDataFromTitle( $dbr, $title, $recency=IDBAccessObject::READ_NORMAL)
Fetch a page record matching the Title object's namespace and title using a sanitized title string.
Definition WikiPage.php:370
loadPageData( $from=IDBAccessObject::READ_NORMAL)
Load the object from a given source by title.
Definition WikiPage.php:412
__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:613
isLocal()
Whether this content displayed on this page comes from the local database.
PreparedEdit false $mPreparedEdit
Map of cache fields (text, parser output, etc.) for a proposed/new edit.
Definition WikiPage.php:122
insertNullProtectionRevision(string $revCommentMsg, array $limit, array $expiry, bool $cascade, string $reason, UserIdentity $user)
Insert a new dummy revision (aka null revision) for this page, to mark a change in page protection.
getUserText( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Definition WikiPage.php:854
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:915
wasLoadedFrom( $from)
Checks whether the page data was loaded using the given database access mode (or better).
Definition WikiPage.php:461
doUserEditContent(Content $content, Authority $performer, $summary, $flags=0, $originalRevId=false, $tags=[], $undidRevId=0)
Change an existing article or create a new article.
doPurge()
Perform the actions of a page purging.
getCurrentUpdate()
Get the state of an ongoing update, shortly before or just after it is saved to the database.
getSourceURL()
Get the source URL for the content on this page, typically the canonical URL, but may be a remote lin...
prepareContentForEdit(Content $content, ?RevisionRecord $revision, UserIdentity $user, $serialFormat=null, $useStash=true)
Prepare content which is about to be saved.
getAutoDeleteReason(&$hasHistory=false)
Auto-generates a deletion reason.
protectDescriptionLog(array $limit, array $expiry)
Builds the description to serve as comment for the log entry.
getComment( $audience=RevisionRecord::FOR_PUBLIC, ?Authority $performer=null)
Definition WikiPage.php:875
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new page object.
Definition WikiPage.php:307
setTimestamp( $ts)
Set the page timestamp (use only to avoid DB queries)
Definition WikiPage.php:800
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:682
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:567
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:3348
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'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
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