MediaWiki master
DerivedPageDataUpdater.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Storage;
8
9use InvalidArgumentException;
10use LogicException;
52use Psr\Log\LoggerAwareInterface;
53use Psr\Log\LoggerInterface;
54use Psr\Log\NullLogger;
55use Wikimedia\Assert\Assert;
59use Wikimedia\Timestamp\TimestampFormat as TS;
60
91class DerivedPageDataUpdater implements LoggerAwareInterface, PreparedUpdate {
92
96 private $user = null;
97
101 private $wikiPage;
102
106 private $parserCache;
107
111 private $revisionStore;
112
116 private $contLang;
117
121 private $jobQueueGroup;
122
126 private $loadbalancerFactory;
127
131 private $hookRunner;
132
136 private $eventDispatcher;
137
141 private $logger;
142
146 private $articleCountMethod;
147
156 private $options = [
157 'changed' => true,
158 // newrev is true if prepareUpdate is handling the creation of a new revision,
159 // as opposed to a null edit or a forced update.
160 'newrev' => false,
161 'created' => false,
162 'oldtitle' => null,
163 'oldrevision' => null,
164 'oldcountable' => null,
165 'oldredirect' => null,
166 'triggeringUser' => null,
167 // causeAction/causeAgent default to 'unknown' but that's handled where it's read,
168 // to make the life of prepareUpdate() callers easier.
169 'causeAction' => null,
170 'causeAgent' => null,
171 'editResult' => null,
172 'rcPatrolStatus' => 0,
173 'tags' => [],
174 'cause' => 'edit',
175 'reason' => null,
176 'emitEvents' => true,
177 ] + PageLatestRevisionChangedEvent::DEFAULT_FLAGS;
178
200 private $pageState = null;
201
205 private $slotsUpdate = null;
206
210 private $parentRevision = null;
211
215 private $revision = null;
216
220 private $renderedRevision = null;
221
223 private $pageLatestRevisionChangedEvent = null;
224
228 private $revisionRenderer;
229
231 private $slotRoleRegistry;
232
236 private $forceEmptyRevision = false;
237
246 private $stage = 'new';
247
256 private const TRANSITIONS = [
257 'new' => [
258 'new' => true,
259 'knows-current' => true,
260 'has-content' => true,
261 'has-revision' => true,
262 ],
263 'knows-current' => [
264 'knows-current' => true,
265 'has-content' => true,
266 'has-revision' => true,
267 ],
268 'has-content' => [
269 'has-content' => true,
270 'has-revision' => true,
271 ],
272 'has-revision' => [
273 'has-revision' => true,
274 'done' => true,
275 ],
276 ];
277
279 private $contentHandlerFactory;
280
282 private $editResultCache;
283
285 private $contentTransformer;
286
288 private $pageEditStash;
289
291 private $mainWANObjectCache;
292
294 private $warmParsoidParserCache;
295
297 private $useRcPatrol;
298
299 private ChangeTagsStore $changeTagsStore;
300
301 public function __construct(
302 ServiceOptions $options,
303 PageIdentity $page,
304 RevisionStore $revisionStore,
305 RevisionRenderer $revisionRenderer,
306 SlotRoleRegistry $slotRoleRegistry,
307 ParserCache $parserCache,
308 JobQueueGroup $jobQueueGroup,
309 Language $contLang,
310 ILBFactory $loadbalancerFactory,
311 IContentHandlerFactory $contentHandlerFactory,
312 HookContainer $hookContainer,
313 DomainEventDispatcher $eventDispatcher,
314 EditResultCache $editResultCache,
315 ContentTransformer $contentTransformer,
316 PageEditStash $pageEditStash,
317 WANObjectCache $mainWANObjectCache,
318 WikiPageFactory $wikiPageFactory,
319 ChangeTagsStore $changeTagsStore
320 ) {
321 // TODO: Remove this cast eventually
322 $this->wikiPage = $wikiPageFactory->newFromTitle( $page );
323
324 $this->parserCache = $parserCache;
325 $this->revisionStore = $revisionStore;
326 $this->revisionRenderer = $revisionRenderer;
327 $this->slotRoleRegistry = $slotRoleRegistry;
328 $this->jobQueueGroup = $jobQueueGroup;
329 $this->contLang = $contLang;
330 // XXX only needed for waiting for replicas to catch up; there should be a narrower
331 // interface for that.
332 $this->loadbalancerFactory = $loadbalancerFactory;
333 $this->contentHandlerFactory = $contentHandlerFactory;
334 $this->hookRunner = new HookRunner( $hookContainer );
335 $this->eventDispatcher = $eventDispatcher;
336 $this->editResultCache = $editResultCache;
337 $this->contentTransformer = $contentTransformer;
338 $this->pageEditStash = $pageEditStash;
339 $this->mainWANObjectCache = $mainWANObjectCache;
340 $this->changeTagsStore = $changeTagsStore;
341
342 $this->logger = new NullLogger();
343
344 $this->warmParsoidParserCache = $options
345 ->get( MainConfigNames::ParsoidCacheConfig )['WarmParsoidParserCache'];
346 $this->useRcPatrol = $options
348 }
349
350 public function setLogger( LoggerInterface $logger ): void {
351 $this->logger = $logger;
352 }
353
362 public function setCause( string $cause ) {
363 // 'cause' is for use in PageLatestRevisionChangedEvent, 'causeAction' is for
364 // use in tracing in updates, jobs, and RevisionRenderer.
365 // Note that PageLatestRevisionChangedEvent uses causes like "edit" and "move", but
366 // the convention for causeAction is to use "page-edit", etc.
367 $this->options['cause'] = $cause;
368 $this->options['causeAction'] = 'page-' . $cause;
369 }
370
376 public function setPerformer( UserIdentity $performer ) {
377 $this->options['triggeringUser'] = $performer;
378 $this->options['causeAgent'] = $performer->getName();
379 }
380
384 private function getCauseForTracing(): array {
385 return [
386 $this->options['causeAction'] ?? 'unknown',
387 $this->options['causeAgent']
388 ?? ( $this->user ? $this->user->getName() : 'unknown' ),
389 ];
390 }
391
400 private function doTransition( $newStage ) {
401 $this->assertTransition( $newStage );
402
403 $oldStage = $this->stage;
404 $this->stage = $newStage;
405
406 return $oldStage;
407 }
408
416 private function assertTransition( $newStage ) {
417 if ( empty( self::TRANSITIONS[$this->stage][$newStage] ) ) {
418 throw new LogicException( "Cannot transition from {$this->stage} to $newStage" );
419 }
420 }
421
433 public function isReusableFor(
434 ?UserIdentity $user = null,
435 ?RevisionRecord $revision = null,
436 ?RevisionSlotsUpdate $slotsUpdate = null,
437 $parentId = null
438 ) {
439 if ( $revision
440 && $parentId
441 && $revision->getParentId() !== $parentId
442 ) {
443 throw new InvalidArgumentException( '$parentId should match the parent of $revision' );
444 }
445
446 // NOTE: For dummy revisions, $user may be different from $this->revision->getUser
447 // and also from $revision->getUser.
448 // But $user should always match $this->user.
449 if ( $user && $this->user && $user->getName() !== $this->user->getName() ) {
450 return false;
451 }
452
453 if ( $revision && $this->revision && $this->revision->getId()
454 && $this->revision->getId() !== $revision->getId()
455 ) {
456 return false;
457 }
458
459 if ( $this->pageState
460 && $revision
461 && $revision->getParentId() !== null
462 && $this->pageState['oldId'] !== $revision->getParentId()
463 ) {
464 return false;
465 }
466
467 if ( $this->pageState
468 && $parentId !== null
469 && $this->pageState['oldId'] !== $parentId
470 ) {
471 return false;
472 }
473
474 // NOTE: this check is the primary reason for having the $this->slotsUpdate field!
475 if ( $this->slotsUpdate
476 && $slotsUpdate
477 && !$this->slotsUpdate->hasSameUpdates( $slotsUpdate )
478 ) {
479 return false;
480 }
481
482 if ( $revision
483 && $this->revision
484 && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
485 ) {
486 return false;
487 }
488
489 return true;
490 }
491
503 public function setForceEmptyRevision( bool $forceEmptyRevision ) {
504 if ( $this->revision ) {
505 throw new LogicException( 'prepareContent() or prepareUpdate() was already called.' );
506 }
507
508 $this->forceEmptyRevision = $forceEmptyRevision;
509 }
510
515 public function setArticleCountMethod( $articleCountMethod ) {
516 $this->articleCountMethod = $articleCountMethod;
517 }
518
522 private function getTitle() {
523 // NOTE: eventually, this won't use WikiPage any more
524 return $this->wikiPage->getTitle();
525 }
526
530 private function getWikiPage() {
531 // NOTE: eventually, this won't use WikiPage any more
532 return $this->wikiPage;
533 }
534
540 public function getPage(): ProperPageIdentity {
541 return $this->wikiPage;
542 }
543
551 public function pageExisted() {
552 $this->assertHasPageState( __METHOD__ );
553
554 return $this->pageState['oldId'] > 0;
555 }
556
566 private function getParentRevision() {
567 $this->assertPrepared( __METHOD__ );
568
569 if ( $this->parentRevision ) {
570 return $this->parentRevision;
571 }
572
573 if ( !$this->pageState['oldId'] ) {
574 // If there was no current revision, there is no parent revision,
575 // since the page didn't exist.
576 return null;
577 }
578
579 $oldId = $this->revision->getParentId();
580 $flags = $this->usePrimary() ? IDBAccessObject::READ_LATEST : 0;
581 $this->parentRevision = $oldId
582 ? $this->revisionStore->getRevisionById( $oldId, $flags )
583 : null;
584
585 return $this->parentRevision;
586 }
587
595 private function getOldRevision() {
596 $this->assertPrepared( __METHOD__ );
597 return $this->pageState['oldRevision'];
598 }
599
620 public function grabCurrentRevision() {
621 if ( $this->pageState ) {
622 return $this->pageState['oldRevision'];
623 }
624
625 $this->assertTransition( 'knows-current' );
626
627 // NOTE: eventually, this won't use WikiPage any more
628 $wikiPage = $this->getWikiPage();
629
630 // Do not call WikiPage::clear(), since the caller may already have caused page data
631 // to be loaded with SELECT FOR UPDATE. Just assert it's loaded now.
632 $wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
633 $current = $wikiPage->getRevisionRecord();
634
635 $this->pageState = [
636 'oldRevision' => $current,
637 'oldId' => $current ? $current->getId() : 0,
638 'oldIsRedirect' => $wikiPage->isRedirect(), // NOTE: uses page table
639 'oldCountable' => $wikiPage->isCountable(), // NOTE: uses pagelinks table
640 'oldRecord' => $wikiPage->exists() ? $wikiPage->toPageRecord() : null,
641 ];
642
643 $this->doTransition( 'knows-current' );
644
645 return $this->pageState['oldRevision'];
646 }
647
653 public function isContentPrepared() {
654 return $this->revision !== null;
655 }
656
664 public function isUpdatePrepared() {
665 return $this->revision !== null && $this->revision->getId() !== null;
666 }
667
671 private function getPageId() {
672 // NOTE: eventually, this won't use WikiPage any more
673 return $this->wikiPage->getId();
674 }
675
681 public function isContentDeleted() {
682 if ( $this->revision ) {
683 return $this->revision->isDeleted( RevisionRecord::DELETED_TEXT );
684 } else {
685 // If the content has not been saved yet, it cannot have been deleted yet.
686 return false;
687 }
688 }
689
699 public function getRawSlot( $role ) {
700 return $this->getSlots()->getSlot( $role );
701 }
702
711 public function getRawContent( string $role ): Content {
712 return $this->getRawSlot( $role )->getContent();
713 }
714
715 private function usePrimary(): bool {
716 // TODO: can we just set a flag to true in prepareContent()?
717 return $this->wikiPage->wasLoadedFrom( IDBAccessObject::READ_LATEST );
718 }
719
720 public function isCountable(): bool {
721 // NOTE: Keep in sync with WikiPage::isCountable.
722
723 if ( !$this->getTitle()->isContentPage() ) {
724 return false;
725 }
726
727 if ( $this->isContentDeleted() ) {
728 // This should be irrelevant: countability only applies to the current revision,
729 // and the current revision is never suppressed.
730 return false;
731 }
732
733 if ( $this->isRedirect() ) {
734 return false;
735 }
736
737 $hasLinks = null;
738
739 if ( $this->articleCountMethod === 'link' ) {
740 // NOTE: it would be more appropriate to determine for each slot separately
741 // whether it has links, and use that information with that slot's
742 // isCountable() method. However, that would break parity with
743 // WikiPage::isCountable, which uses the pagelinks table to determine
744 // whether the current revision has links.
745 $hasLinks = $this->getParserOutputForMetaData()->hasLinks();
746 }
747
748 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
749 $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
750 if ( $roleHandler->supportsArticleCount() ) {
751 $content = $this->getRawContent( $role );
752
753 if ( $content->isCountable( $hasLinks ) ) {
754 return true;
755 }
756 }
757 }
758
759 return false;
760 }
761
762 public function isRedirect(): bool {
763 // NOTE: main slot determines redirect status
764 // TODO: MCR: this should be controlled by a PageTypeHandler
765 $mainContent = $this->getRawContent( SlotRecord::MAIN );
766
767 return $mainContent->isRedirect();
768 }
769
775 private function revisionIsRedirect( RevisionRecord $rev ) {
776 // NOTE: main slot determines redirect status
777 $mainContent = $rev->getMainContentRaw();
778
779 return $mainContent->isRedirect();
780 }
781
805 public function prepareContent(
806 UserIdentity $user,
807 RevisionSlotsUpdate $slotsUpdate,
808 $useStash = true
809 ) {
810 if ( $this->slotsUpdate ) {
811 if ( !$this->user ) {
812 throw new LogicException(
813 'Unexpected state: $this->slotsUpdate was initialized, '
814 . 'but $this->user was not.'
815 );
816 }
817
818 if ( $this->user->getName() !== $user->getName() ) {
819 throw new LogicException( 'Can\'t call prepareContent() again for different user! '
820 . 'Expected ' . $this->user->getName() . ', got ' . $user->getName()
821 );
822 }
823
824 if ( !$this->slotsUpdate->hasSameUpdates( $slotsUpdate ) ) {
825 throw new LogicException(
826 'Can\'t call prepareContent() again with different slot content!'
827 );
828 }
829
830 return; // prepareContent() already done, nothing to do
831 }
832
833 $this->assertTransition( 'has-content' );
834
835 $wikiPage = $this->getWikiPage(); // TODO: use only for legacy hooks!
836 $title = $this->getTitle();
837
838 $parentRevision = $this->grabCurrentRevision();
839
840 // The edit may have already been prepared via api.php?action=stashedit
841 $stashedEdit = false;
842
843 // TODO: MCR: allow output for all slots to be stashed.
844 if ( $useStash && $slotsUpdate->isModifiedSlot( SlotRecord::MAIN ) ) {
845 $stashedEdit = $this->pageEditStash->checkCache(
846 $title,
847 $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent(),
848 $user
849 );
850 }
851
852 $userPopts = ParserOptions::newFromUserAndLang( $user, $this->contLang );
853 $userPopts->setRenderReason( $this->options['causeAgent'] ?? 'unknown' );
854
855 $this->hookRunner->onArticlePrepareTextForEdit( $wikiPage, $userPopts );
856
857 $this->user = $user;
858 $this->slotsUpdate = $slotsUpdate;
859
860 if ( $parentRevision ) {
861 $this->revision = MutableRevisionRecord::newFromParentRevision( $parentRevision );
862 } else {
863 $this->revision = new MutableRevisionRecord( $title );
864 }
865
866 // NOTE: user and timestamp must be set, so they can be used for
867 // {{subst:REVISIONUSER}} and {{subst:REVISIONTIMESTAMP}} in PST!
868 $this->revision->setTimestamp( MWTimestamp::now( TS::MW ) );
869 $this->revision->setUser( $user );
870
871 // Set up ParserOptions to operate on the new revision
872 $oldCallback = $userPopts->getCurrentRevisionRecordCallback();
873 $userPopts->setCurrentRevisionRecordCallback(
874 function ( Title $parserTitle, $parser = null ) use ( $title, $oldCallback ) {
875 if ( $parserTitle->equals( $title ) ) {
876 return $this->revision;
877 } else {
878 return $oldCallback( $parserTitle, $parser );
879 }
880 }
881 );
882
883 $pstContentSlots = $this->revision->getSlots();
884
885 foreach ( $slotsUpdate->getModifiedRoles() as $role ) {
886 $slot = $slotsUpdate->getModifiedSlot( $role );
887
888 if ( $slot->isInherited() ) {
889 // No PST for inherited slots! Note that "modified" slots may still be inherited
890 // from an earlier version, e.g. for rollbacks.
891 $pstSlot = $slot;
892 } elseif ( $role === SlotRecord::MAIN && $stashedEdit ) {
893 // TODO: MCR: allow PST content for all slots to be stashed.
894 $pstSlot = SlotRecord::newUnsaved( $role, $stashedEdit->pstContent );
895 } else {
896 $pstContent = $this->contentTransformer->preSaveTransform(
897 $slot->getContent(),
898 $title,
899 $user,
900 $userPopts
901 );
902
903 $pstSlot = SlotRecord::newUnsaved( $role, $pstContent );
904 }
905
906 $pstContentSlots->setSlot( $pstSlot );
907 }
908
909 foreach ( $slotsUpdate->getRemovedRoles() as $role ) {
910 $pstContentSlots->removeSlot( $role );
911 }
912
913 $this->options['created'] = ( $parentRevision === null );
914 $this->options['changed'] = ( $parentRevision === null
915 || !$pstContentSlots->hasSameContent( $parentRevision->getSlots() ) );
916
917 $this->doTransition( 'has-content' );
918
919 if ( !$this->options['changed'] ) {
920 if ( $this->forceEmptyRevision ) {
921 // dummy revision, inherit all slots
922 foreach ( $parentRevision->getSlotRoles() as $role ) {
923 $this->revision->inheritSlot( $parentRevision->getSlot( $role ) );
924 }
925 } else {
926 // null-edit, the new revision *is* the old revision.
927
928 // TODO: move this into MutableRevisionRecord
929 $this->revision->setId( $parentRevision->getId() );
930 $this->revision->setTimestamp( $parentRevision->getTimestamp() );
931 $this->revision->setPageId( $parentRevision->getPageId() );
932 $this->revision->setParentId( $parentRevision->getParentId() );
933 $this->revision->setUser( $parentRevision->getUser( RevisionRecord::RAW ) );
934 $this->revision->setComment( $parentRevision->getComment( RevisionRecord::RAW ) );
935 $this->revision->setMinorEdit( $parentRevision->isMinor() );
936 $this->revision->setVisibility( $parentRevision->getVisibility() );
937
938 // prepareUpdate() is redundant for null-edits (but not for dummy revisions)
939 $this->doTransition( 'has-revision' );
940 }
941 } else {
942 $this->parentRevision = $parentRevision;
943 }
944
945 $renderHints = [ 'use-master' => $this->usePrimary(), 'audience' => RevisionRecord::RAW ];
946
947 if ( $stashedEdit ) {
949 $output = $stashedEdit->output;
950 // TODO: this should happen when stashing the ParserOutput, not now!
951 $output->setCacheTime( $stashedEdit->timestamp );
952
953 $renderHints['known-revision-output'] = $output;
954
955 $this->logger->debug( __METHOD__ . ': using stashed edit output...' );
956 }
957
958 $renderHints['generate-html'] = $this->shouldGenerateHTMLOnEdit();
959
960 [ $causeAction, ] = $this->getCauseForTracing();
961 $renderHints['causeAction'] = $causeAction;
962
963 // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
964 // NOTE: the revision is either new or current, so we can bypass audience checks.
965 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
966 $this->revision,
967 null,
968 null,
969 $renderHints
970 );
971 }
972
988 public function getRevision(): RevisionRecord {
989 $this->assertPrepared( __METHOD__ );
990 return $this->revision;
991 }
992
994 $this->assertPrepared( __METHOD__ );
995
996 return $this->renderedRevision;
997 }
998
999 private function assertHasPageState( string $method ) {
1000 if ( !$this->pageState ) {
1001 throw new LogicException(
1002 'Must call grabCurrentRevision() or prepareContent() '
1003 . 'or prepareUpdate() before calling ' . $method
1004 );
1005 }
1006 }
1007
1008 private function assertPrepared( string $method ) {
1009 if ( !$this->revision ) {
1010 throw new LogicException(
1011 'Must call prepareContent() or prepareUpdate() before calling ' . $method
1012 );
1013 }
1014 }
1015
1016 private function assertHasRevision( string $method ) {
1017 if ( !$this->revision->getId() ) {
1018 throw new LogicException(
1019 'Must call prepareUpdate() before calling ' . $method
1020 );
1021 }
1022 }
1023
1029 public function isCreation() {
1030 $this->assertPrepared( __METHOD__ );
1031 return $this->options['created'];
1032 }
1033
1046 public function isChange() {
1047 $this->assertPrepared( __METHOD__ );
1048 return $this->options['changed'];
1049 }
1050
1056 public function wasRedirect() {
1057 $this->assertHasPageState( __METHOD__ );
1058
1059 if ( $this->pageState['oldIsRedirect'] === null ) {
1061 $rev = $this->pageState['oldRevision'];
1062 if ( $rev ) {
1063 $this->pageState['oldIsRedirect'] = $this->revisionIsRedirect( $rev );
1064 } else {
1065 $this->pageState['oldIsRedirect'] = false;
1066 }
1067 }
1068
1069 return $this->pageState['oldIsRedirect'];
1070 }
1071
1080 public function getSlots() {
1081 $this->assertPrepared( __METHOD__ );
1082 return $this->revision->getSlots();
1083 }
1084
1090 private function getRevisionSlotsUpdate() {
1091 $this->assertPrepared( __METHOD__ );
1092
1093 if ( !$this->slotsUpdate ) {
1094 $old = $this->getParentRevision();
1095 $this->slotsUpdate = RevisionSlotsUpdate::newFromRevisionSlots(
1096 $this->revision->getSlots(),
1097 $old ? $old->getSlots() : null
1098 );
1099 }
1100 return $this->slotsUpdate;
1101 }
1102
1109 public function getTouchedSlotRoles() {
1110 return $this->getRevisionSlotsUpdate()->getTouchedRoles();
1111 }
1112
1119 public function getModifiedSlotRoles(): array {
1120 return $this->getRevisionSlotsUpdate()->getModifiedRoles();
1121 }
1122
1128 public function getRemovedSlotRoles(): array {
1129 return $this->getRevisionSlotsUpdate()->getRemovedRoles();
1130 }
1131
1177 public function prepareUpdate( RevisionRecord $revision, array $options = [] ) {
1178 Assert::parameter(
1179 !isset( $options['oldrevision'] )
1180 || $options['oldrevision'] instanceof RevisionRecord,
1181 '$options["oldrevision"]',
1182 'must be a RevisionRecord'
1183 );
1184 Assert::parameter(
1185 !isset( $options['triggeringUser'] )
1186 || $options['triggeringUser'] instanceof UserIdentity,
1187 '$options["triggeringUser"]',
1188 'must be a UserIdentity'
1189 );
1190 Assert::parameter(
1191 !isset( $options['editResult'] )
1192 || $options['editResult'] instanceof EditResult,
1193 '$options["editResult"]',
1194 'must be an EditResult'
1195 );
1196
1197 if ( !$revision->getId() ) {
1198 throw new InvalidArgumentException(
1199 'Revision must have an ID set for it to be used with prepareUpdate()!'
1200 );
1201 }
1202
1203 if ( !$this->wikiPage->exists() ) {
1204 // If the ongoing edit is creating the page, the state of $this->wikiPage
1205 // may be out of whack. This would only happen if the page creation was
1206 // done using a different WikiPage instance, which shouldn't be the case.
1207 $this->logger->warning(
1208 __METHOD__ . ': Reloading page meta-data after page creation',
1209 [
1210 'page' => (string)$this->wikiPage,
1211 'rev_id' => $revision->getId(),
1212 ]
1213 );
1214
1215 $this->wikiPage->clear();
1216 $this->wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
1217 }
1218
1219 if ( $this->revision && $this->revision->getId() ) {
1220 if ( $this->revision->getId() === $revision->getId() ) {
1221 $this->options['changed'] = false; // null-edit
1222 } else {
1223 throw new LogicException(
1224 'Trying to re-use DerivedPageDataUpdater with revision '
1225 . $revision->getId()
1226 . ', but it\'s already bound to revision '
1227 . $this->revision->getId()
1228 );
1229 }
1230 }
1231
1232 if ( $this->revision
1233 && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
1234 ) {
1235 throw new LogicException(
1236 'The revision provided has mismatching content!'
1237 );
1238 }
1239
1240 // Override fields defined in $this->options with values from $options.
1241 $this->options = array_intersect_key( $options, $this->options ) + $this->options;
1242
1243 if ( $this->revision ) {
1244 $oldId = $this->pageState['oldId'] ?? 0;
1245 $this->options['newrev'] = ( $revision->getId() !== $oldId );
1246 } elseif ( isset( $this->options['oldrevision'] ) ) {
1248 $oldRev = $this->options['oldrevision'];
1249 $oldId = $oldRev->getId();
1250 $this->options['newrev'] = ( $revision->getId() !== $oldId );
1251 } else {
1252 $oldId = $revision->getParentId();
1253 }
1254
1255 if ( $oldId !== null ) {
1256 // XXX: what if $options['changed'] disagrees?
1257 // MovePage creates a dummy revision with changed = false!
1258 // We may want to explicitly distinguish between "no new revision" (null-edit)
1259 // and "new revision without new content" (dummy revision).
1260
1261 if ( $oldId === $revision->getParentId() ) {
1262 // NOTE: this may still be a dummy revision!
1263 // New revision!
1264 $this->options['changed'] = true;
1265 } elseif ( $oldId === $revision->getId() ) {
1266 // Null-edit!
1267 $this->options['changed'] = false;
1268 } else {
1269 // This indicates that calling code has given us the wrong RevisionRecord object
1270 throw new LogicException(
1271 'The RevisionRecord mismatches old revision ID: '
1272 . 'Old ID is ' . $oldId
1273 . ', parent ID is ' . $revision->getParentId()
1274 . ', revision ID is ' . $revision->getId()
1275 );
1276 }
1277 }
1278
1279 // If prepareContent() was used to generate the PST content (which is indicated by
1280 // $this->slotsUpdate being set), and this is not a null-edit, then the given
1281 // revision must have the acting user as the revision author. Otherwise, user
1282 // signatures generated by PST would mismatch the user in the revision record.
1283 if ( $this->user !== null && $this->options['changed'] && $this->slotsUpdate ) {
1284 $user = $revision->getUser();
1285 if ( !$this->user->equals( $user ) ) {
1286 throw new LogicException(
1287 'The RevisionRecord provided has a mismatching actor: expected '
1288 . $this->user->getName()
1289 . ', got '
1290 . $user->getName()
1291 );
1292 }
1293 }
1294
1295 // If $this->pageState was not yet initialized by grabCurrentRevision or prepareContent,
1296 // emulate the state of the page table before the edit, as good as we can.
1297 if ( !$this->pageState ) {
1298 $this->pageState = [
1299 'oldIsRedirect' => isset( $this->options['oldredirect'] )
1300 && is_bool( $this->options['oldredirect'] )
1301 ? $this->options['oldredirect']
1302 : null,
1303 'oldCountable' => isset( $this->options['oldcountable'] )
1304 && is_bool( $this->options['oldcountable'] )
1305 ? $this->options['oldcountable']
1306 : null,
1307 ];
1308
1309 if ( $this->options['changed'] ) {
1310 // The edit created a new revision
1311 $this->pageState['oldId'] = $revision->getParentId();
1312 // Old revision is null if this is a page creation
1313 $this->pageState['oldRevision'] = $this->options['oldrevision'] ?? null;
1314 } else {
1315 // This is a null-edit, so the old revision IS the new revision!
1316 $this->pageState['oldId'] = $revision->getId();
1317 $this->pageState['oldRevision'] = $revision;
1318 }
1319 }
1320
1321 // "created" is forced here
1322 $this->options['created'] = ( $this->options['created'] ||
1323 ( $this->pageState['oldId'] === 0 ) );
1324
1325 $this->revision = $revision;
1326
1327 $this->doTransition( 'has-revision' );
1328
1329 // NOTE: in case we have a User object, don't override with a UserIdentity.
1330 // We already checked that $revision->getUser() matches $this->user;
1331 if ( !$this->user ) {
1332 $this->user = $revision->getUser( RevisionRecord::RAW );
1333 }
1334
1335 // Prune any output that depends on the revision ID.
1336 if ( $this->renderedRevision ) {
1337 $this->renderedRevision->updateRevision( $revision );
1338 } else {
1339 [ $causeAction, ] = $this->getCauseForTracing();
1340 // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
1341 // NOTE: the revision is either new or current, so we can bypass audience checks.
1342 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
1343 $this->revision,
1344 null,
1345 null,
1346 [
1347 'use-master' => $this->usePrimary(),
1348 'audience' => RevisionRecord::RAW,
1349 'known-revision-output' => $options['known-revision-output'] ?? null,
1350 'causeAction' => $causeAction
1351 ]
1352 );
1353
1354 // XXX: Since we presumably are dealing with the current revision,
1355 // we could try to get the ParserOutput from the parser cache.
1356 }
1357
1358 // TODO: optionally get ParserOutput from the ParserCache here.
1359 // Move the logic used by RefreshLinksJob here!
1360 }
1361
1366 public function getPreparedEdit() {
1367 $this->assertPrepared( __METHOD__ );
1368
1369 $slotsUpdate = $this->getRevisionSlotsUpdate();
1370 $preparedEdit = new PreparedEdit();
1371
1372 $preparedEdit->popts = $this->getCanonicalParserOptions();
1373 $preparedEdit->parserOutputCallback = $this->getCanonicalParserOutput( ... );
1374 $preparedEdit->pstContent = $this->revision->getContent( SlotRecord::MAIN );
1375 $preparedEdit->newContent =
1376 $slotsUpdate->isModifiedSlot( SlotRecord::MAIN )
1377 ? $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent()
1378 : $this->revision->getContent( SlotRecord::MAIN ); // XXX: can we just remove this?
1379 $preparedEdit->oldContent = null; // unused. // XXX: could get this from the parent revision
1380 $preparedEdit->revid = $this->revision ? $this->revision->getId() : null;
1381 $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1382
1383 return $preparedEdit;
1384 }
1385
1391 public function getSlotParserOutput( $role, $generateHtml = true ) {
1392 return $this->getRenderedRevision()->getSlotParserOutput(
1393 $role,
1394 [ 'generate-html' => $generateHtml ]
1395 );
1396 }
1397
1403 return $this->getRenderedRevision()->getRevisionParserOutput( [ 'generate-html' => false ] );
1404 }
1405
1411 return $this->getRenderedRevision()->getRevisionParserOutput();
1412 }
1413
1415 return $this->getRenderedRevision()->getOptions();
1416 }
1417
1423 public function getSecondaryDataUpdates( $recursive = false ) {
1424 if ( $this->isContentDeleted() ) {
1425 // This shouldn't happen, since the current content is always public,
1426 // and DataUpdates are only needed for current content.
1427 return [];
1428 }
1429
1430 $wikiPage = $this->getWikiPage();
1431 $wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
1432 if ( !$wikiPage->exists() ) {
1433 // page deleted while deferring the update
1434 return [];
1435 }
1436
1437 $title = $wikiPage->getTitle();
1438 $allUpdates = [];
1439 $parserOutput = $this->shouldGenerateHTMLOnEdit() ?
1440 $this->getCanonicalParserOutput() : $this->getParserOutputForMetaData();
1441
1442 // Construct a LinksUpdate for the combined canonical output.
1443 $linksUpdate = new LinksUpdate(
1444 $title,
1445 $parserOutput,
1446 $recursive,
1447 // Redirect target may have changed if the page is or was a redirect.
1448 // (We can't check if it was definitely changed without additional queries.)
1449 $this->isRedirect() || $this->wasRedirect()
1450 );
1451 if ( $this->options['cause'] === PageLatestRevisionChangedEvent::CAUSE_MOVE ) {
1452 $linksUpdate->setMoveDetails( $this->options['oldtitle'] );
1453 }
1454
1455 $allUpdates[] = $linksUpdate;
1456 // NOTE: Run updates for all slots, not just the modified slots! Otherwise,
1457 // info for an inherited slot may end up being removed. This is also needed
1458 // to ensure that purges are effective.
1459 $renderedRevision = $this->getRenderedRevision();
1460
1461 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
1462 $slot = $this->getRawSlot( $role );
1463 $content = $slot->getContent();
1464 $handler = $content->getContentHandler();
1465
1466 $updates = $handler->getSecondaryDataUpdates(
1467 $title,
1468 $content,
1469 $role,
1470 $renderedRevision
1471 );
1472
1473 $allUpdates = array_merge( $allUpdates, $updates );
1474 }
1475
1476 // XXX: if a slot was removed by an earlier edit, but deletion updates failed to run at
1477 // that time, we don't know for which slots to run deletion updates when purging a page.
1478 // We'd have to examine the entire history of the page to determine that. Perhaps there
1479 // could be a "try extra hard" mode for that case that would run a DB query to find all
1480 // roles/models ever used on the page. On the other hand, removing slots should be quite
1481 // rare, so perhaps this isn't worth the trouble.
1482
1483 // TODO: consolidate with similar logic in WikiPage::getDeletionUpdates()
1484 $parentRevision = $this->getParentRevision();
1485 foreach ( $this->getRemovedSlotRoles() as $role ) {
1486 // HACK: we should get the content model of the removed slot from a SlotRoleHandler!
1487 // For now, find the slot in the parent revision - if the slot was removed, it should
1488 // always exist in the parent revision.
1489 $parentSlot = $parentRevision->getSlot( $role, RevisionRecord::RAW );
1490 $content = $parentSlot->getContent();
1491 $handler = $content->getContentHandler();
1492
1493 $updates = $handler->getDeletionUpdates(
1494 $title,
1495 $role
1496 );
1497
1498 $allUpdates = array_merge( $allUpdates, $updates );
1499 }
1500
1501 // TODO: hard deprecate SecondaryDataUpdates in favor of RevisionDataUpdates in 1.33!
1502 $this->hookRunner->onRevisionDataUpdates( $title, $renderedRevision, $allUpdates );
1503
1504 return $allUpdates;
1505 }
1506
1511 private function shouldGenerateHTMLOnEdit(): bool {
1512 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
1513 $slot = $this->getRawSlot( $role );
1514 $contentHandler = $this->contentHandlerFactory->getContentHandler( $slot->getModel() );
1515 if ( $contentHandler->generateHTMLOnEdit() ) {
1516 return true;
1517 }
1518 }
1519 return false;
1520 }
1521
1536 public function doUpdates() {
1537 $this->assertTransition( 'done' );
1538
1539 $this->emitEventsIfNeeded();
1540
1541 // TODO: move more logic into ingress objects subscribed to PageLatestRevisionChangedEvent!
1542 $event = $this->getPageLatestRevisionChangedEvent();
1543
1544 if ( $this->shouldGenerateHTMLOnEdit() ) {
1545 $this->triggerParserCacheUpdate();
1546 }
1547
1548 $this->doSecondaryDataUpdates( [
1549 // T52785 do not update any other pages on dummy revisions and null edits
1550 'recursive' => $event->isEffectiveContentChange(),
1551 // Defer the getCanonicalParserOutput() call made by getSecondaryDataUpdates()
1552 'defer' => DeferredUpdates::POSTSEND
1553 ] );
1554
1555 $id = $this->getPageId();
1556 $title = $this->getTitle();
1557 $wikiPage = $this->getWikiPage();
1558
1559 if ( !$title->exists() ) {
1560 wfDebug( __METHOD__ . ": Page doesn't exist any more, bailing out" );
1561
1562 $this->doTransition( 'done' );
1563 return;
1564 }
1565
1566 DeferredUpdates::addCallableUpdate( function () use ( $event ) {
1567 if (
1568 $this->options['oldcountable'] === 'no-change' ||
1569 ( !$event->isEffectiveContentChange()
1570 && !$event->hasCause( PageLatestRevisionChangedEvent::CAUSE_MOVE ) )
1571 ) {
1572 $good = 0;
1573 } elseif ( $event->isCreation() ) {
1574 $good = (int)$this->isCountable();
1575 } elseif ( $this->options['oldcountable'] !== null ) {
1576 $good = (int)$this->isCountable()
1577 - (int)$this->options['oldcountable'];
1578 } elseif ( isset( $this->pageState['oldCountable'] ) ) {
1579 $good = (int)$this->isCountable()
1580 - (int)$this->pageState['oldCountable'];
1581 } else {
1582 $good = 0;
1583 }
1584 $edits = $event->isEffectiveContentChange() ? 1 : 0;
1585 $pages = $event->isCreation() ? 1 : 0;
1586
1587 DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
1588 [ 'edits' => $edits, 'articles' => $good, 'pages' => $pages ]
1589 ) );
1590 } );
1591
1592 // TODO: move onArticleCreate and onArticleEdit into a PageEventEmitter service
1593 if ( $event->isCreation() ) {
1594 // Deferred update that adds a mw-recreated tag to edits that create new pages
1595 // which have an associated deletion log entry for the specific namespace/title combination
1596 // and which are not undeletes
1597 if ( !( $event->hasCause( PageLatestRevisionChangedEvent::CAUSE_UNDELETE ) ) ) {
1598 $revision = $this->revision;
1599 DeferredUpdates::addCallableUpdate( function () use ( $revision, $wikiPage ) {
1600 $this->maybeAddRecreateChangeTag( $wikiPage, $revision->getId() );
1601 } );
1602 }
1603 WikiPage::onArticleCreate( $title, $this->isRedirect() );
1604 } elseif ( $event->isEffectiveContentChange() ) { // T52785
1605 // TODO: Check $event->isNominalContentChange() instead so we still
1606 // trigger updates on null edits, but pass a flag to suppress
1607 // backlink purges through queueBacklinksJobs() id
1608 // $event->changedLatestRevisionId() returns false.
1609 WikiPage::onArticleEdit(
1610 $title,
1611 $this->revision,
1612 $this->getTouchedSlotRoles(),
1613 // Redirect target may have changed if the page is or was a redirect.
1614 // (We can't check if it was definitely changed without additional queries.)
1615 $this->isRedirect() || $this->wasRedirect()
1616 );
1617 }
1618
1619 if ( $event->hasCause( PageLatestRevisionChangedEvent::CAUSE_UNDELETE ) ) {
1620 $this->mainWANObjectCache->touchCheckKey(
1621 "DerivedPageDataUpdater:restore:page:$id"
1622 );
1623 }
1624
1625 $editResult = $event->getEditResult();
1626
1627 if ( $editResult && !$editResult->isNullEdit() ) {
1628 // Cache EditResult for future use, via
1629 // RevertTagUpdateManager::approveRevertedTagForRevision().
1630 // This drives RevertedTagUpdateManager::approveRevertedTagForRevision.
1631 // It is only needed if RCPatrolling is enabled and the edit is a revert.
1632 // Skip in other cases to avoid flooding the cache, see T386217 and T388573.
1633 if ( $editResult->isRevert() && $this->useRcPatrol ) {
1634 $this->editResultCache->set(
1635 $this->revision->getId(),
1636 $editResult
1637 );
1638 }
1639 }
1640
1641 $this->doTransition( 'done' );
1642 }
1643
1644 private function emitEventsIfNeeded(): void {
1645 if ( !$this->options['emitEvents'] ) {
1646 return;
1647 }
1648
1649 $this->emitEvents();
1650 }
1651
1655 public function emitEvents(): void {
1656 if ( !( $this->options['allowEvents'] ?? true ) ) {
1657 throw new LogicException( 'dispatchPageUpdatedEvent was disabled on this updater' );
1658 }
1659
1660 // don't dispatch again!
1661 $this->options['emitEvents'] = false;
1662 $this->options['allowEvents'] = false;
1663
1664 $pageLatestRevisionChangedEvent = $this->getPageLatestRevisionChangedEvent();
1665 $pageCreatedEvent = $this->getPageCreatedEvent();
1666
1667 if (
1668 $pageLatestRevisionChangedEvent->getPageRecordBefore() === null &&
1669 !$this->options['created']
1670 ) {
1671 // if the page wasn't just created, we need the state before
1672 throw new LogicException( 'Missing page state before update' );
1673 }
1674
1675 $this->eventDispatcher->dispatch(
1676 $pageLatestRevisionChangedEvent,
1677 $this->loadbalancerFactory
1678 );
1679
1680 if ( $pageCreatedEvent ) {
1681 // NOTE: Emit PageCreated after PageLatestRevisionChanged, because the creation
1682 // is only finished after the revision has been set.
1683 $this->eventDispatcher->dispatch( $pageCreatedEvent, $this->loadbalancerFactory );
1684 }
1685 }
1686
1687 private function getNominalPerformer(): UserIdentity {
1689 $performer = $this->options['triggeringUser'] ?? $this->user;
1690 '@phan-var UserIdentity $performer';
1691
1692 return $performer;
1693 }
1694
1695 private function getPageLatestRevisionChangedEvent(): PageLatestRevisionChangedEvent {
1696 if ( $this->pageLatestRevisionChangedEvent ) {
1697 return $this->pageLatestRevisionChangedEvent;
1698 }
1699
1700 $this->assertHasRevision( __METHOD__ );
1701
1702 $flags = array_intersect_key(
1703 $this->options,
1704 PageLatestRevisionChangedEvent::DEFAULT_FLAGS
1705 );
1706
1707 $pageRecordBefore = $this->pageState['oldRecord'] ?? null;
1708 $pageRecordAfter = $this->getWikiPage()->toPageRecord();
1709
1710 $revisionBefore = $this->getOldRevision();
1711 $revisionAfter = $this->getRevision();
1712
1713 if ( $this->options['created'] ) {
1714 // Page creation. No prior state.
1715 // Force null to make sure we don't get confused during imports when
1716 // updates are triggered after importing the last revision of several.
1717 // In that case, the page and older revisions do already exist when
1718 // the DerivedPageDataUpdater is initialized, because they were
1719 // created during the import. But they didn't exist prior to the
1720 // import (based on the fact that the 'created' flag is set).
1721 $pageRecordBefore = null;
1722 $revisionBefore = null;
1723 } elseif ( !$this->options['changed'] ) {
1724 // Null edit. Should already be the same, just make sure.
1725 $pageRecordBefore = $pageRecordAfter;
1726 }
1727
1728 if ( $revisionBefore && $revisionAfter->getId() === $revisionBefore->getId() ) {
1729 // This is a null edit, flag it as a reconciliation request.
1730 $flags[ PageLatestRevisionChangedEvent::FLAG_RECONCILIATION_REQUEST ] = true;
1731 }
1732
1733 if ( $pageRecordBefore === null && !$this->options['created'] ) {
1734 // If the page wasn't just created, we need the state before.
1735 // If we are not actually emitting the event, we can ignore the issue.
1736 // This is needed to support the deprecated WikiPage::doEditUpdates()
1737 // method. Once that is gone, we can remove this conditional.
1738 if ( $this->options['emitEvents'] ) {
1739 throw new LogicException( 'Missing page state before update' );
1740 }
1741 }
1742
1743 $this->pageLatestRevisionChangedEvent = new PageLatestRevisionChangedEvent(
1744 $this->options['cause'] ?? PageUpdateCauses::CAUSE_EDIT,
1745 $pageRecordBefore,
1746 $pageRecordAfter,
1747 $revisionBefore,
1748 $revisionAfter,
1749 $this->getRevisionSlotsUpdate(),
1750 $this->options['editResult'] ?? null,
1751 $this->getNominalPerformer(),
1752 $this->options['tags'] ?? [],
1753 $flags,
1754 $this->options['rcPatrolStatus'] ?? 0,
1755 );
1756
1757 return $this->pageLatestRevisionChangedEvent;
1758 }
1759
1760 private function getPageCreatedEvent(): ?PageCreatedEvent {
1761 if ( !$this->options['created'] ) {
1762 return null;
1763 }
1764
1765 $pageRecordAfter = $this->getWikiPage()->toPageRecord();
1766
1767 return new PageCreatedEvent(
1768 $this->options['cause'] ?? PageUpdateCauses::CAUSE_EDIT,
1769 $pageRecordAfter,
1770 $this->getRevision(),
1771 $this->getNominalPerformer(),
1772 $this->options['reason'] ?? $this->getRevision()->getComment()->text,
1773 );
1774 }
1775
1776 private function triggerParserCacheUpdate() {
1777 $this->assertHasRevision( __METHOD__ );
1778
1779 $userParserOptions = ParserOptions::newFromUser( $this->user );
1780
1781 // Decide whether to save the final canonical parser output based on the fact that
1782 // users are typically redirected to viewing pages right after they edit those pages.
1783 // Due to vary-revision-id, getting/saving that output here might require a reparse.
1784 if ( $userParserOptions->matchesForCacheKey( $this->getCanonicalParserOptions() ) ) {
1785 // Whether getting the final output requires a reparse or not, the user will
1786 // need canonical output anyway, since that is what their parser options use.
1787 // A reparse now at least has the benefit of various warm process caches.
1788 $this->doParserCacheUpdate();
1789 } else {
1790 // If the user does not have canonical parse options, then don't risk another parse
1791 // to make output they cannot use on the page refresh that typically occurs after
1792 // editing. Doing the parser output save post-send will still benefit *other* users.
1793 DeferredUpdates::addCallableUpdate( function () {
1794 $this->doParserCacheUpdate();
1795 } );
1796 }
1797 }
1798
1807 private function maybeAddRecreateChangeTag( WikiPage $wikiPage, int $revisionId ) {
1808 $dbr = $this->loadbalancerFactory->getReplicaDatabase();
1809
1810 if ( $dbr->newSelectQueryBuilder()
1811 ->select( [ '1' ] )
1812 ->from( 'logging' )
1813 ->where( [
1814 'log_type' => 'delete',
1815 'log_title' => $wikiPage->getTitle()->getDBkey(),
1816 'log_namespace' => $wikiPage->getNamespace(),
1817 ] )
1818 ->where(
1819 $dbr->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) .
1820 ' != ' . LogPage::DELETED_ACTION // T385792
1821 )->caller( __METHOD__ )->limit( 1 )->fetchField() ) {
1822 $this->changeTagsStore->addTags(
1823 [ ChangeTags::TAG_RECREATE ],
1824 null,
1825 $revisionId );
1826 }
1827 }
1828
1844 public function doSecondaryDataUpdates( array $options = [] ) {
1845 $this->assertHasRevision( __METHOD__ );
1846 $options += [ 'recursive' => false, 'defer' => false, 'freshness' => false ];
1847 $deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
1848 if ( !in_array( $options['defer'], $deferValues, true ) ) {
1849 throw new InvalidArgumentException( 'Invalid value for defer: ' . $options['defer'] );
1850 }
1851
1852 $triggeringUser = $this->options['triggeringUser'] ?? $this->user;
1853 [ $causeAction, $causeAgent ] = $this->getCauseForTracing();
1854 if ( isset( $options['known-revision-output'] ) ) {
1855 $this->getRenderedRevision()->setRevisionParserOutput( $options['known-revision-output'] );
1856 }
1857
1858 // Bundle all of the data updates into a single deferred update wrapper so that
1859 // any failure will cause at most one refreshLinks job to be enqueued by
1860 // DeferredUpdates::doUpdates(). This is hard to do when there are many separate
1861 // updates that are not defined as being related.
1862 $update = new RefreshSecondaryDataUpdate(
1863 $this->loadbalancerFactory,
1864 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Already checked
1865 $triggeringUser,
1866 $this->wikiPage,
1867 $this->revision,
1868 $this,
1869 [ 'recursive' => $options['recursive'], 'freshness' => $options['freshness'] ]
1870 );
1871 $update->setCause( $causeAction, $causeAgent );
1872
1873 if ( $options['defer'] === false ) {
1874 DeferredUpdates::attemptUpdate( $update );
1875 } else {
1876 DeferredUpdates::addUpdate( $update, $options['defer'] );
1877 }
1878 }
1879
1886 public function doParserCacheUpdate() {
1887 $this->assertHasRevision( __METHOD__ );
1888
1889 $wikiPage = $this->getWikiPage(); // TODO: ParserCache should accept a RevisionRecord instead
1890
1891 // NOTE: this may trigger the first parsing of the new content after an edit (when not
1892 // using pre-generated stashed output).
1893 // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse
1894 // to be performed post-send. The client could already follow a HTTP redirect to the
1895 // page view, but would then have to wait for a response until rendering is complete.
1896 $output = $this->getCanonicalParserOutput();
1897
1898 // Save it to the parser cache. Use the revision timestamp in the case of a
1899 // freshly saved edit, as that matches page_touched and a mismatch would trigger an
1900 // unnecessary reparse.
1901 $timestamp = $this->options['newrev'] ? $this->revision->getTimestamp()
1902 : $output->getCacheTime();
1903 $this->parserCache->save(
1904 $output, $wikiPage, $this->getCanonicalParserOptions(),
1905 $timestamp, $this->revision->getId()
1906 );
1907
1908 // If we enable cache warming with parsoid outputs, let's do it at the same
1909 // time we're populating the parser cache with pre-generated HTML.
1910 // Use OPT_FORCE_PARSE to avoid a useless cache lookup.
1911 if ( $this->warmParsoidParserCache ) {
1912 $cacheWarmingParams = $this->getCauseForTracing();
1913 $cacheWarmingParams['options'] = ParserOutputAccess::OPT_FORCE_PARSE;
1914
1915 $this->jobQueueGroup->lazyPush(
1916 ParsoidCachePrewarmJob::newSpec(
1917 $this->revision->getId(),
1918 $wikiPage->toPageRecord(),
1919 $cacheWarmingParams
1920 )
1921 );
1922 }
1923 }
1924
1925}
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
Read-write access to the change_tags table.
Recent changes tagging.
A class for passing options to services.
Defer callable updates to run later in the PHP process.
Class the manages updates of *_link tables as well as similar extension-managed tables.
Update object handling the cleanup of secondary data after a page was edited.
Class for handling updates to the site_stats table.
Represents information returned by WikiPage::prepareContentForEdit()
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Handle enqueueing of background jobs.
Base class for language-specific code.
Definition Language.php:69
Class to simplify the use of log pages.
Definition LogPage.php:35
A class containing constants representing the names of configuration variables.
const UseRCPatrol
Name constant for the UseRCPatrol setting, for use with Config::get()
const ParsoidCacheConfig
Name constant for the ParsoidCacheConfig setting, for use with Config::get()
Domain event representing page creation.
Domain event representing a change to the page's latest revision.
getPageRecordBefore()
Returns a PageRecord representing the state of the page before the change, or null if the page did no...
Service for getting rendered output of a given page.
Service for creating WikiPage objects.
newFromTitle(PageReference $pageReference)
Create a WikiPage object from a title.
Base representation for an editable wiki page.
Definition WikiPage.php:82
loadPageData( $from='fromdb')
Load the object from a given source by title.
Definition WikiPage.php:410
getRevisionRecord()
Get the latest revision.
Definition WikiPage.php:743
isRedirect()
Is the page a redirect, according to secondary tracking tables? If this is true, getRedirectTarget() ...
Definition WikiPage.php:563
getTitle()
Get the title object of the article.
Definition WikiPage.php:250
getId( $wikiId=self::LOCAL)
Definition WikiPage.php:526
toPageRecord()
Returns the page represented by this WikiPage as a PageStoreRecord.
isCountable( $editInfo=false)
Whether the page may count towards the the site's number of "articles".
Definition WikiPage.php:901
Cache for ParserOutput objects corresponding to the latest page revisions.
Set options of the Parser.
ParserOutput is a rendering of a Content object or a message.
RenderedRevision represents the rendered representation of a revision.
Page revision base class.
getUser( $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Fetch revision's author's user identity, if it's available to the specified audience.
getParentId( $wikiId=self::LOCAL)
Get parent revision ID (the original previous page revision).
getMainContentRaw()
Returns the Content of the main slot of this revision.
getSlots()
Returns the slots defined for this revision.
getId( $wikiId=self::LOCAL)
Get revision ID.
The RevisionRenderer service provides access to rendered output for revisions.
Value object representing the set of slots belonging to a revision.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
A handle for managing updates for derived page data on edit, import, purge, etc.
doParserCacheUpdate()
Causes parser cache entries to be updated.
getModifiedSlotRoles()
Returns the role names of the slots modified by the new revision, not including removed roles.
setPerformer(UserIdentity $performer)
Set the performer of the action.
setCause(string $cause)
Set the cause of the update.
__construct(ServiceOptions $options, PageIdentity $page, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, SlotRoleRegistry $slotRoleRegistry, ParserCache $parserCache, JobQueueGroup $jobQueueGroup, Language $contLang, ILBFactory $loadbalancerFactory, IContentHandlerFactory $contentHandlerFactory, HookContainer $hookContainer, DomainEventDispatcher $eventDispatcher, EditResultCache $editResultCache, ContentTransformer $contentTransformer, PageEditStash $pageEditStash, WANObjectCache $mainWANObjectCache, WikiPageFactory $wikiPageFactory, ChangeTagsStore $changeTagsStore)
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (e.g.
isContentDeleted()
Whether the content is deleted and thus not visible to the public.
prepareUpdate(RevisionRecord $revision, array $options=[])
Prepare derived data updates targeting the given RevisionRecord.
isCreation()
Whether the edit creates the page.
isReusableFor(?UserIdentity $user=null, ?RevisionRecord $revision=null, ?RevisionSlotsUpdate $slotsUpdate=null, $parentId=null)
Checks whether this DerivedPageDataUpdater can be re-used for running updates targeting the given rev...
getRemovedSlotRoles()
Returns the role names of the slots removed by the new revision.
getRawContent(string $role)
Returns the content of the given slot, with no audience checks.
prepareContent(UserIdentity $user, RevisionSlotsUpdate $slotsUpdate, $useStash=true)
Prepare updates based on an update which has not yet been saved.
getCanonicalParserOutput()
Returns the canonical parser output.Code that does not need access to the rendered HTML should use ge...
isChange()
Whether the content of the current revision after the edit is different from the content of the curre...
grabCurrentRevision()
Returns the revision that was the page's current revision when grabCurrentRevision() was first called...
doUpdates()
Do standard updates after page edit, purge, or import.
pageExisted()
Determines whether the page being edited already existed.
getRenderedRevision()
Returns a RenderedRevision instance acting as a lazy holder for the ParserOutput of the revision.
wasRedirect()
Whether the page was a redirect before the edit.
isUpdatePrepared()
Whether prepareUpdate() has been called on this instance.
getSlots()
Returns the slots of the target revision, after PST.
getTouchedSlotRoles()
Returns the role names of the slots touched by the new revision, including removed roles.
isRedirect()
Whether the page will be a redirect after the edit.
isContentPrepared()
Whether prepareUpdate() or prepareContent() have been called on this instance.
getRawSlot( $role)
Returns the slot, modified or inherited, after PST, with no audience checks applied.
setForceEmptyRevision(bool $forceEmptyRevision)
Set whether null-edits should create a revision.
isCountable()
Whether the page will be countable after the edit.
getRevision()
Returns the update's target revision - that is, the revision that will be the current revision after ...
Class allowing easy storage and retrieval of EditResults associated with revisions.
Object for storing information about the effects of an edit.
Manage the pre-emptive page parsing for edits to wiki pages.
Value object representing a modification of revision slots.
getRemovedRoles()
Returns a list of removed slot roles, that is, roles removed by calling removeSlot(),...
getModifiedRoles()
Returns a list of modified slot roles, that is, roles modified by calling modifySlot(),...
getModifiedSlot( $role)
Returns the SlotRecord associated with the given role, if the slot with that role was modified (and n...
isModifiedSlot( $role)
Returns whether getModifiedSlot() will return a SlotRecord for the given role.
Represents a title within MediaWiki.
Definition Title.php:69
equals(object $other)
Compares with another Title.
Definition Title.php:3086
Library for creating and parsing MW-style timestamps.
Multi-datacenter aware caching 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'=> false, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 250, 300,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'EnableSpecialMute'=> false, 'EnableUserEmailMuteList'=> false, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ImageLinksSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> '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, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, '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', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ImageLinksSchemaMigrationStage' => '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', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'RCEngines' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', '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', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], 'required' => [ 'url', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Content objects represent page content, e.g.
Definition Content.php:28
Interface that deferrable updates should implement.
Service for sending domain events to registered listeners.
Interface for objects (potentially) representing an editable wiki page.
Interface for a page that is (or could be, or used to be) an editable wiki page.
An object representing a page update during an edit.
Interface for objects representing user identity.
Interface for database access objects.
Manager of ILoadBalancer objects and, indirectly, IDatabase connections.