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
93 private ?UserIdentity $user = null;
94 private readonly WikiPage $wikiPage;
95 private readonly HookRunner $hookRunner;
96 private LoggerInterface $logger;
97
101 private ?string $articleCountMethod;
102
110 private array $options = [
111 'changed' => true,
112 // newrev is true if prepareUpdate is handling the creation of a new revision,
113 // as opposed to a null edit or a forced update.
114 'newrev' => false,
115 'created' => false,
116 'oldtitle' => null,
117 'oldrevision' => null,
118 'oldcountable' => null,
119 'oldredirect' => null,
120 'triggeringUser' => null,
121 // causeAction/causeAgent default to 'unknown' but that's handled where it's read,
122 // to make the life of prepareUpdate() callers easier.
123 'causeAction' => null,
124 'causeAgent' => null,
125 'editResult' => null,
126 'rcPatrolStatus' => 0,
127 'tags' => [],
128 'cause' => 'edit',
129 'reason' => null,
130 'emitEvents' => true,
131 ] + PageLatestRevisionChangedEvent::DEFAULT_FLAGS;
132
154 private $pageState = null;
155 private ?RevisionSlotsUpdate $slotsUpdate = null;
156 private ?RevisionRecord $parentRevision = null;
157 private ?RevisionRecord $revision = null;
158 private ?RenderedRevision $renderedRevision = null;
159 private ?PageLatestRevisionChangedEvent $pageLatestRevisionChangedEvent = null;
160
164 private bool $forceEmptyRevision = false;
165
172 private string $stage = 'new';
173
182 private const TRANSITIONS = [
183 'new' => [
184 'new' => true,
185 'knows-current' => true,
186 'has-content' => true,
187 'has-revision' => true,
188 ],
189 'knows-current' => [
190 'knows-current' => true,
191 'has-content' => true,
192 'has-revision' => true,
193 ],
194 'has-content' => [
195 'has-content' => true,
196 'has-revision' => true,
197 ],
198 'has-revision' => [
199 'has-revision' => true,
200 'done' => true,
201 ],
202 ];
203
204 private readonly bool $warmParsoidParserCache;
205 private readonly bool $useRcPatrol;
206
207 public function __construct(
208 ServiceOptions $options,
209 PageIdentity $page,
210 private readonly RevisionStore $revisionStore,
211 private readonly RevisionRenderer $revisionRenderer,
212 private readonly SlotRoleRegistry $slotRoleRegistry,
213 private readonly ParserCache $parserCache,
214 private readonly JobQueueGroup $jobQueueGroup,
215 private readonly Language $contLang,
216 private readonly ILBFactory $loadbalancerFactory,
217 private readonly IContentHandlerFactory $contentHandlerFactory,
218 HookContainer $hookContainer,
219 private readonly DomainEventDispatcher $eventDispatcher,
220 private readonly EditResultCache $editResultCache,
221 private readonly ContentTransformer $contentTransformer,
222 private readonly PageEditStash $pageEditStash,
223 private readonly WANObjectCache $mainWANObjectCache,
224 WikiPageFactory $wikiPageFactory,
225 private readonly ChangeTagsStore $changeTagsStore,
226 ) {
227 // TODO: Remove this cast eventually
228 $this->wikiPage = $wikiPageFactory->newFromTitle( $page );
229
230 $this->hookRunner = new HookRunner( $hookContainer );
231
232 $this->logger = new NullLogger();
233
234 $this->warmParsoidParserCache = $options
235 ->get( MainConfigNames::ParsoidCacheConfig )['WarmParsoidParserCache'];
236 $this->useRcPatrol = $options
237 ->get( MainConfigNames::UseRCPatrol ) ?? false;
238 }
239
240 public function setLogger( LoggerInterface $logger ): void {
241 $this->logger = $logger;
242 }
243
252 public function setCause( string $cause ) {
253 // 'cause' is for use in PageLatestRevisionChangedEvent, 'causeAction' is for
254 // use in tracing in updates, jobs, and RevisionRenderer.
255 // Note that PageLatestRevisionChangedEvent uses causes like "edit" and "move", but
256 // the convention for causeAction is to use "page-edit", etc.
257 $this->options['cause'] = $cause;
258 $this->options['causeAction'] = 'page-' . $cause;
259 }
260
266 public function setPerformer( UserIdentity $performer ) {
267 $this->options['triggeringUser'] = $performer;
268 $this->options['causeAgent'] = $performer->getName();
269 }
270
274 private function getCauseForTracing(): array {
275 return [
276 $this->options['causeAction'] ?? 'unknown',
277 $this->options['causeAgent']
278 ?? ( $this->user ? $this->user->getName() : 'unknown' ),
279 ];
280 }
281
290 private function doTransition( $newStage ) {
291 $this->assertTransition( $newStage );
292
293 $oldStage = $this->stage;
294 $this->stage = $newStage;
295
296 return $oldStage;
297 }
298
306 private function assertTransition( $newStage ) {
307 if ( empty( self::TRANSITIONS[$this->stage][$newStage] ) ) {
308 throw new LogicException( "Cannot transition from {$this->stage} to $newStage" );
309 }
310 }
311
323 public function isReusableFor(
324 ?UserIdentity $user = null,
325 ?RevisionRecord $revision = null,
326 ?RevisionSlotsUpdate $slotsUpdate = null,
327 $parentId = null
328 ) {
329 if ( $revision
330 && $parentId
331 && $revision->getParentId() !== $parentId
332 ) {
333 throw new InvalidArgumentException( '$parentId should match the parent of $revision' );
334 }
335
336 // NOTE: For dummy revisions, $user may be different from $this->revision->getUser
337 // and also from $revision->getUser.
338 // But $user should always match $this->user.
339 if ( $user && $this->user && $user->getName() !== $this->user->getName() ) {
340 return false;
341 }
342
343 if ( $revision && $this->revision && $this->revision->getId()
344 && $this->revision->getId() !== $revision->getId()
345 ) {
346 return false;
347 }
348
349 if ( $this->pageState
350 && $revision
351 && $revision->getParentId() !== null
352 && $this->pageState['oldId'] !== $revision->getParentId()
353 ) {
354 return false;
355 }
356
357 if ( $this->pageState
358 && $parentId !== null
359 && $this->pageState['oldId'] !== $parentId
360 ) {
361 return false;
362 }
363
364 // NOTE: this check is the primary reason for having the $this->slotsUpdate field!
365 if ( $this->slotsUpdate
366 && $slotsUpdate
367 && !$this->slotsUpdate->hasSameUpdates( $slotsUpdate )
368 ) {
369 return false;
370 }
371
372 if ( $revision
373 && $this->revision
374 && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
375 ) {
376 return false;
377 }
378
379 return true;
380 }
381
393 public function setForceEmptyRevision( bool $forceEmptyRevision ) {
394 if ( $this->revision ) {
395 throw new LogicException( 'prepareContent() or prepareUpdate() was already called.' );
396 }
397
398 $this->forceEmptyRevision = $forceEmptyRevision;
399 }
400
405 public function setArticleCountMethod( ?string $articleCountMethod ) {
406 $this->articleCountMethod = $articleCountMethod;
407 }
408
412 private function getTitle() {
413 // NOTE: eventually, this won't use WikiPage any more
414 return $this->wikiPage->getTitle();
415 }
416
420 private function getWikiPage() {
421 // NOTE: eventually, this won't use WikiPage any more
422 return $this->wikiPage;
423 }
424
430 public function getPage(): ProperPageIdentity {
431 return $this->wikiPage;
432 }
433
441 public function pageExisted() {
442 $this->assertHasPageState( __METHOD__ );
443
444 return $this->pageState['oldId'] > 0;
445 }
446
456 private function getParentRevision() {
457 $this->assertPrepared( __METHOD__ );
458
459 if ( $this->parentRevision ) {
460 return $this->parentRevision;
461 }
462
463 if ( !$this->pageState['oldId'] ) {
464 // If there was no latest revision, there is no parent revision,
465 // since the page didn't exist.
466 return null;
467 }
468
469 $oldId = $this->revision->getParentId();
470 $flags = $this->usePrimary() ? IDBAccessObject::READ_LATEST : 0;
471 $this->parentRevision = $oldId
472 ? $this->revisionStore->getRevisionById( $oldId, $flags )
473 : null;
474
475 return $this->parentRevision;
476 }
477
485 private function getOldRevision() {
486 $this->assertPrepared( __METHOD__ );
487 return $this->pageState['oldRevision'];
488 }
489
511 public function grabLatestRevision() {
512 if ( $this->pageState ) {
513 return $this->pageState['oldRevision'];
514 }
515
516 $this->assertTransition( 'knows-current' );
517
518 // NOTE: eventually, this won't use WikiPage any more
519 $wikiPage = $this->getWikiPage();
520
521 // Do not call WikiPage::clear(), since the caller may already have caused page data
522 // to be loaded with SELECT FOR UPDATE. Just assert it's loaded now.
523 $wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
524 $current = $wikiPage->getRevisionRecord();
525
526 $this->pageState = [
527 'oldRevision' => $current,
528 'oldId' => $current ? $current->getId() : 0,
529 'oldIsRedirect' => $wikiPage->isRedirect(), // NOTE: uses page table
530 'oldCountable' => $wikiPage->isCountable(), // NOTE: uses pagelinks table
531 'oldRecord' => $wikiPage->exists() ? $wikiPage->toPageRecord() : null,
532 ];
533
534 $this->doTransition( 'knows-current' );
535
536 return $this->pageState['oldRevision'];
537 }
538
543 public function grabCurrentRevision() {
544 return $this->grabLatestRevision();
545 }
546
552 public function isContentPrepared() {
553 return $this->revision !== null;
554 }
555
563 public function isUpdatePrepared() {
564 return $this->revision !== null && $this->revision->getId() !== null;
565 }
566
570 private function getPageId() {
571 // NOTE: eventually, this won't use WikiPage any more
572 return $this->wikiPage->getId();
573 }
574
580 public function isContentDeleted() {
581 if ( $this->revision ) {
582 return $this->revision->isDeleted( RevisionRecord::DELETED_TEXT );
583 } else {
584 // If the content has not been saved yet, it cannot have been deleted yet.
585 return false;
586 }
587 }
588
598 public function getRawSlot( $role ) {
599 return $this->getSlots()->getSlot( $role );
600 }
601
610 public function getRawContent( string $role ): Content {
611 return $this->getRawSlot( $role )->getContent();
612 }
613
614 private function usePrimary(): bool {
615 // TODO: can we just set a flag to true in prepareContent()?
616 return $this->wikiPage->wasLoadedFrom( IDBAccessObject::READ_LATEST );
617 }
618
619 public function isCountable(): bool {
620 // NOTE: Keep in sync with WikiPage::isCountable.
621
622 if ( !$this->getTitle()->isContentPage() ) {
623 return false;
624 }
625
626 if ( $this->isContentDeleted() ) {
627 // This should be irrelevant: countability only applies to the latest revision,
628 // and the latest revision is never suppressed.
629 return false;
630 }
631
632 if ( $this->isRedirect() ) {
633 return false;
634 }
635
636 $hasLinks = null;
637
638 if ( $this->articleCountMethod === 'link' ) {
639 // NOTE: it would be more appropriate to determine for each slot separately
640 // whether it has links, and use that information with that slot's
641 // isCountable() method. However, that would break parity with
642 // WikiPage::isCountable, which uses the pagelinks table to determine
643 // whether the latest revision has links.
644 $hasLinks = $this->getParserOutputForMetaData()->hasLinks();
645 }
646
647 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
648 $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
649 if ( $roleHandler->supportsArticleCount() ) {
650 $content = $this->getRawContent( $role );
651
652 if ( $content->isCountable( $hasLinks ) ) {
653 return true;
654 }
655 }
656 }
657
658 return false;
659 }
660
661 public function isRedirect(): bool {
662 // NOTE: main slot determines redirect status
663 // TODO: MCR: this should be controlled by a PageTypeHandler
664 $mainContent = $this->getRawContent( SlotRecord::MAIN );
665
666 return $mainContent->isRedirect();
667 }
668
674 private function revisionIsRedirect( RevisionRecord $rev ) {
675 // NOTE: main slot determines redirect status
676 $mainContent = $rev->getMainContentRaw();
677
678 return $mainContent->isRedirect();
679 }
680
704 public function prepareContent(
705 UserIdentity $user,
706 RevisionSlotsUpdate $slotsUpdate,
707 $useStash = true
708 ) {
709 if ( $this->slotsUpdate ) {
710 if ( !$this->user ) {
711 throw new LogicException(
712 'Unexpected state: $this->slotsUpdate was initialized, '
713 . 'but $this->user was not.'
714 );
715 }
716
717 if ( $this->user->getName() !== $user->getName() ) {
718 throw new LogicException( 'Can\'t call prepareContent() again for different user! '
719 . 'Expected ' . $this->user->getName() . ', got ' . $user->getName()
720 );
721 }
722
723 if ( !$this->slotsUpdate->hasSameUpdates( $slotsUpdate ) ) {
724 throw new LogicException(
725 'Can\'t call prepareContent() again with different slot content!'
726 );
727 }
728
729 return; // prepareContent() already done, nothing to do
730 }
731
732 $this->assertTransition( 'has-content' );
733
734 $wikiPage = $this->getWikiPage(); // TODO: use only for legacy hooks!
735 $title = $this->getTitle();
736
737 $parentRevision = $this->grabLatestRevision();
738
739 // The edit may have already been prepared via api.php?action=stashedit
740 $stashedEdit = false;
741
742 // TODO: MCR: allow output for all slots to be stashed.
743 if ( $useStash && $slotsUpdate->isModifiedSlot( SlotRecord::MAIN ) ) {
744 $stashedEdit = $this->pageEditStash->checkCache(
745 $title,
746 $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent(),
747 $user
748 );
749 }
750
751 $userPopts = ParserOptions::newFromUserAndLang( $user, $this->contLang );
752 $userPopts->setRenderReason( $this->options['causeAgent'] ?? 'unknown' );
753
754 $this->hookRunner->onArticlePrepareTextForEdit( $wikiPage, $userPopts );
755
756 $this->user = $user;
757 $this->slotsUpdate = $slotsUpdate;
758
759 if ( $parentRevision ) {
760 $this->revision = MutableRevisionRecord::newFromParentRevision( $parentRevision );
761 } else {
762 $this->revision = new MutableRevisionRecord( $title );
763 }
764
765 // NOTE: user and timestamp must be set, so they can be used for
766 // {{subst:REVISIONUSER}} and {{subst:REVISIONTIMESTAMP}} in PST!
767 $this->revision->setTimestamp( MWTimestamp::now( TS::MW ) );
768 $this->revision->setUser( $user );
769
770 // Set up ParserOptions to operate on the new revision
771 $oldCallback = $userPopts->getCurrentRevisionRecordCallback();
772 $userPopts->setCurrentRevisionRecordCallback(
773 function ( Title $parserTitle, $parser = null ) use ( $title, $oldCallback ) {
774 if ( $parserTitle->equals( $title ) ) {
775 return $this->revision;
776 } else {
777 return $oldCallback( $parserTitle, $parser );
778 }
779 }
780 );
781
782 $pstContentSlots = $this->revision->getSlots();
783
784 foreach ( $slotsUpdate->getModifiedRoles() as $role ) {
785 $slot = $slotsUpdate->getModifiedSlot( $role );
786
787 if ( $slot->isInherited() ) {
788 // No PST for inherited slots! Note that "modified" slots may still be inherited
789 // from an earlier version, e.g. for rollbacks.
790 $pstSlot = $slot;
791 } elseif ( $role === SlotRecord::MAIN && $stashedEdit ) {
792 // TODO: MCR: allow PST content for all slots to be stashed.
793 $pstSlot = SlotRecord::newUnsaved( $role, $stashedEdit->pstContent );
794 } else {
795 $pstContent = $this->contentTransformer->preSaveTransform(
796 $slot->getContent(),
797 $title,
798 $user,
799 $userPopts
800 );
801
802 $pstSlot = SlotRecord::newUnsaved( $role, $pstContent );
803 }
804
805 $pstContentSlots->setSlot( $pstSlot );
806 }
807
808 foreach ( $slotsUpdate->getRemovedRoles() as $role ) {
809 $pstContentSlots->removeSlot( $role );
810 }
811
812 $this->options['created'] = ( $parentRevision === null );
813 $this->options['changed'] = ( $parentRevision === null
814 || !$pstContentSlots->hasSameContent( $parentRevision->getSlots() ) );
815
816 $this->doTransition( 'has-content' );
817
818 if ( !$this->options['changed'] ) {
819 if ( $this->forceEmptyRevision ) {
820 // dummy revision, inherit all slots
821 foreach ( $parentRevision->getSlotRoles() as $role ) {
822 $this->revision->inheritSlot( $parentRevision->getSlot( $role ) );
823 }
824 } else {
825 // null-edit, the new revision *is* the old revision.
826
827 // TODO: move this into MutableRevisionRecord
828 $this->revision->setId( $parentRevision->getId() );
829 $this->revision->setTimestamp( $parentRevision->getTimestamp() );
830 $this->revision->setPageId( $parentRevision->getPageId() );
831 $this->revision->setParentId( $parentRevision->getParentId() );
832 $this->revision->setUser( $parentRevision->getUser( RevisionRecord::RAW ) );
833 $this->revision->setComment( $parentRevision->getComment( RevisionRecord::RAW ) );
834 $this->revision->setMinorEdit( $parentRevision->isMinor() );
835 $this->revision->setVisibility( $parentRevision->getVisibility() );
836
837 // prepareUpdate() is redundant for null-edits (but not for dummy revisions)
838 $this->doTransition( 'has-revision' );
839 }
840 } else {
841 $this->parentRevision = $parentRevision;
842 }
843
844 $renderHints = [ 'use-master' => $this->usePrimary(), 'audience' => RevisionRecord::RAW ];
845
846 if ( $stashedEdit ) {
848 $output = $stashedEdit->output;
849 // TODO: this should happen when stashing the ParserOutput, not now!
850 $output->setCacheTime( $stashedEdit->timestamp );
851
852 $renderHints['known-revision-output'] = $output;
853
854 $this->logger->debug( __METHOD__ . ': using stashed edit output...' );
855 }
856
857 $renderHints['generate-html'] = $this->shouldGenerateHTMLOnEdit();
858
859 [ $causeAction, ] = $this->getCauseForTracing();
860 $renderHints['causeAction'] = $causeAction;
861
862 // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
863 // NOTE: the revision is either new or current, so we can bypass audience checks.
864 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
865 $this->revision,
866 null,
867 null,
868 $renderHints
869 );
870 }
871
887 public function getRevision(): RevisionRecord {
888 $this->assertPrepared( __METHOD__ );
889 return $this->revision;
890 }
891
893 $this->assertPrepared( __METHOD__ );
894
895 return $this->renderedRevision;
896 }
897
898 private function assertHasPageState( string $method ) {
899 if ( !$this->pageState ) {
900 throw new LogicException(
901 'Must call grabLatestRevision() or prepareContent() '
902 . 'or prepareUpdate() before calling ' . $method
903 );
904 }
905 }
906
907 private function assertPrepared( string $method ) {
908 if ( !$this->revision ) {
909 throw new LogicException(
910 'Must call prepareContent() or prepareUpdate() before calling ' . $method
911 );
912 }
913 }
914
915 private function assertHasRevision( string $method ) {
916 if ( !$this->revision->getId() ) {
917 throw new LogicException(
918 'Must call prepareUpdate() before calling ' . $method
919 );
920 }
921 }
922
928 public function isCreation() {
929 $this->assertPrepared( __METHOD__ );
930 return $this->options['created'];
931 }
932
945 public function isChange() {
946 $this->assertPrepared( __METHOD__ );
947 return $this->options['changed'];
948 }
949
955 public function wasRedirect() {
956 $this->assertHasPageState( __METHOD__ );
957
958 if ( $this->pageState['oldIsRedirect'] === null ) {
960 $rev = $this->pageState['oldRevision'];
961 if ( $rev ) {
962 $this->pageState['oldIsRedirect'] = $this->revisionIsRedirect( $rev );
963 } else {
964 $this->pageState['oldIsRedirect'] = false;
965 }
966 }
967
968 return $this->pageState['oldIsRedirect'];
969 }
970
979 public function getSlots() {
980 $this->assertPrepared( __METHOD__ );
981 return $this->revision->getSlots();
982 }
983
989 private function getRevisionSlotsUpdate() {
990 $this->assertPrepared( __METHOD__ );
991
992 if ( !$this->slotsUpdate ) {
993 $old = $this->getParentRevision();
994 $this->slotsUpdate = RevisionSlotsUpdate::newFromRevisionSlots(
995 $this->revision->getSlots(),
996 $old ? $old->getSlots() : null
997 );
998 }
999 return $this->slotsUpdate;
1000 }
1001
1008 public function getTouchedSlotRoles() {
1009 return $this->getRevisionSlotsUpdate()->getTouchedRoles();
1010 }
1011
1018 public function getModifiedSlotRoles(): array {
1019 return $this->getRevisionSlotsUpdate()->getModifiedRoles();
1020 }
1021
1027 public function getRemovedSlotRoles(): array {
1028 return $this->getRevisionSlotsUpdate()->getRemovedRoles();
1029 }
1030
1076 public function prepareUpdate( RevisionRecord $revision, array $options = [] ) {
1077 Assert::parameter(
1078 !isset( $options['oldrevision'] )
1079 || $options['oldrevision'] instanceof RevisionRecord,
1080 '$options["oldrevision"]',
1081 'must be a RevisionRecord'
1082 );
1083 Assert::parameter(
1084 !isset( $options['triggeringUser'] )
1085 || $options['triggeringUser'] instanceof UserIdentity,
1086 '$options["triggeringUser"]',
1087 'must be a UserIdentity'
1088 );
1089 Assert::parameter(
1090 !isset( $options['editResult'] )
1091 || $options['editResult'] instanceof EditResult,
1092 '$options["editResult"]',
1093 'must be an EditResult'
1094 );
1095
1096 if ( !$revision->getId() ) {
1097 throw new InvalidArgumentException(
1098 'Revision must have an ID set for it to be used with prepareUpdate()!'
1099 );
1100 }
1101
1102 if ( !$this->wikiPage->exists() ) {
1103 // If the ongoing edit is creating the page, the state of $this->wikiPage
1104 // may be out of whack. This would only happen if the page creation was
1105 // done using a different WikiPage instance, which shouldn't be the case.
1106 $this->logger->warning(
1107 __METHOD__ . ': Reloading page meta-data after page creation',
1108 [
1109 'page' => (string)$this->wikiPage,
1110 'rev_id' => $revision->getId(),
1111 ]
1112 );
1113
1114 $this->wikiPage->clear();
1115 $this->wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
1116 }
1117
1118 if ( $this->revision && $this->revision->getId() ) {
1119 if ( $this->revision->getId() === $revision->getId() ) {
1120 $this->options['changed'] = false; // null-edit
1121 } else {
1122 throw new LogicException(
1123 'Trying to re-use DerivedPageDataUpdater with revision '
1124 . $revision->getId()
1125 . ', but it\'s already bound to revision '
1126 . $this->revision->getId()
1127 );
1128 }
1129 }
1130
1131 if ( $this->revision
1132 && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
1133 ) {
1134 throw new LogicException(
1135 'The revision provided has mismatching content!'
1136 );
1137 }
1138
1139 // Override fields defined in $this->options with values from $options.
1140 $this->options = array_intersect_key( $options, $this->options ) + $this->options;
1141
1142 if ( $this->revision ) {
1143 $oldId = $this->pageState['oldId'] ?? 0;
1144 $this->options['newrev'] = ( $revision->getId() !== $oldId );
1145 } elseif ( isset( $this->options['oldrevision'] ) ) {
1147 $oldRev = $this->options['oldrevision'];
1148 $oldId = $oldRev->getId();
1149 $this->options['newrev'] = ( $revision->getId() !== $oldId );
1150 } else {
1151 $oldId = $revision->getParentId();
1152 }
1153
1154 if ( $oldId !== null ) {
1155 // XXX: what if $options['changed'] disagrees?
1156 // MovePage creates a dummy revision with changed = false!
1157 // We may want to explicitly distinguish between "no new revision" (null-edit)
1158 // and "new revision without new content" (dummy revision).
1159
1160 if ( $oldId === $revision->getParentId() ) {
1161 // NOTE: this may still be a dummy revision!
1162 // New revision!
1163 $this->options['changed'] = true;
1164 } elseif ( $oldId === $revision->getId() ) {
1165 // Null-edit!
1166 $this->options['changed'] = false;
1167 } else {
1168 // This indicates that calling code has given us the wrong RevisionRecord object
1169 throw new LogicException(
1170 'The RevisionRecord mismatches old revision ID: '
1171 . 'Old ID is ' . $oldId
1172 . ', parent ID is ' . $revision->getParentId()
1173 . ', revision ID is ' . $revision->getId()
1174 );
1175 }
1176 }
1177
1178 // If prepareContent() was used to generate the PST content (which is indicated by
1179 // $this->slotsUpdate being set), and this is not a null-edit, then the given
1180 // revision must have the acting user as the revision author. Otherwise, user
1181 // signatures generated by PST would mismatch the user in the revision record.
1182 if ( $this->user !== null && $this->options['changed'] && $this->slotsUpdate ) {
1183 $user = $revision->getUser();
1184 if ( !$this->user->equals( $user ) ) {
1185 throw new LogicException(
1186 'The RevisionRecord provided has a mismatching actor: expected '
1187 . $this->user->getName()
1188 . ', got '
1189 . $user->getName()
1190 );
1191 }
1192 }
1193
1194 // If $this->pageState was not yet initialized by grabLatestRevision() or prepareContent(),
1195 // emulate the state of the page table before the edit, as good as we can.
1196 if ( !$this->pageState ) {
1197 $this->pageState = [
1198 'oldIsRedirect' => isset( $this->options['oldredirect'] )
1199 && is_bool( $this->options['oldredirect'] )
1200 ? $this->options['oldredirect']
1201 : null,
1202 'oldCountable' => isset( $this->options['oldcountable'] )
1203 && is_bool( $this->options['oldcountable'] )
1204 ? $this->options['oldcountable']
1205 : null,
1206 ];
1207
1208 if ( $this->options['changed'] ) {
1209 // The edit created a new revision
1210 $this->pageState['oldId'] = $revision->getParentId();
1211 // Old revision is null if this is a page creation
1212 $this->pageState['oldRevision'] = $this->options['oldrevision'] ?? null;
1213 } else {
1214 // This is a null-edit, so the old revision IS the new revision!
1215 $this->pageState['oldId'] = $revision->getId();
1216 $this->pageState['oldRevision'] = $revision;
1217 }
1218 }
1219
1220 // "created" is forced here
1221 $this->options['created'] = ( $this->options['created'] ||
1222 ( $this->pageState['oldId'] === 0 ) );
1223
1224 $this->revision = $revision;
1225
1226 $this->doTransition( 'has-revision' );
1227
1228 // NOTE: in case we have a User object, don't override with a UserIdentity.
1229 // We already checked that $revision->getUser() matches $this->user;
1230 if ( !$this->user ) {
1231 $this->user = $revision->getUser( RevisionRecord::RAW );
1232 }
1233
1234 // Prune any output that depends on the revision ID.
1235 if ( $this->renderedRevision ) {
1236 $this->renderedRevision->updateRevision( $revision );
1237 } else {
1238 [ $causeAction, ] = $this->getCauseForTracing();
1239 // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
1240 // NOTE: the revision is either new or current, so we can bypass audience checks.
1241 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
1242 $this->revision,
1243 null,
1244 null,
1245 [
1246 'use-master' => $this->usePrimary(),
1247 'audience' => RevisionRecord::RAW,
1248 'known-revision-output' => $options['known-revision-output'] ?? null,
1249 'causeAction' => $causeAction
1250 ]
1251 );
1252
1253 // XXX: Since we presumably are dealing with the latest revision,
1254 // we could try to get the ParserOutput from the parser cache.
1255 }
1256
1257 // TODO: optionally get ParserOutput from the ParserCache here.
1258 // Move the logic used by RefreshLinksJob here!
1259 }
1260
1265 public function getPreparedEdit() {
1266 $this->assertPrepared( __METHOD__ );
1267
1268 $slotsUpdate = $this->getRevisionSlotsUpdate();
1269 $preparedEdit = new PreparedEdit();
1270
1271 $preparedEdit->popts = $this->getCanonicalParserOptions();
1272 $preparedEdit->parserOutputCallback = $this->getCanonicalParserOutput( ... );
1273 $preparedEdit->pstContent = $this->revision->getContent( SlotRecord::MAIN );
1274 $preparedEdit->newContent =
1275 $slotsUpdate->isModifiedSlot( SlotRecord::MAIN )
1276 ? $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent()
1277 : $this->revision->getContent( SlotRecord::MAIN ); // XXX: can we just remove this?
1278 $preparedEdit->oldContent = null; // unused. // XXX: could get this from the parent revision
1279 $preparedEdit->revid = $this->revision ? $this->revision->getId() : null;
1280 $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1281
1282 return $preparedEdit;
1283 }
1284
1290 public function getSlotParserOutput( $role, $generateHtml = true ) {
1291 return $this->getRenderedRevision()->getSlotParserOutput(
1292 $role,
1293 [ 'generate-html' => $generateHtml ]
1294 );
1295 }
1296
1302 return $this->getRenderedRevision()->getRevisionParserOutput( [ 'generate-html' => false ] );
1303 }
1304
1310 return $this->getRenderedRevision()->getRevisionParserOutput();
1311 }
1312
1314 return $this->getRenderedRevision()->getOptions();
1315 }
1316
1322 public function getSecondaryDataUpdates( $recursive = false ) {
1323 if ( $this->isContentDeleted() ) {
1324 // This shouldn't happen, since the current content is always public,
1325 // and DataUpdates are only needed for current content.
1326 return [];
1327 }
1328
1329 $wikiPage = $this->getWikiPage();
1330 $wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
1331 if ( !$wikiPage->exists() ) {
1332 // page deleted while deferring the update
1333 return [];
1334 }
1335
1336 $title = $wikiPage->getTitle();
1337 $allUpdates = [];
1338 $parserOutput = $this->shouldGenerateHTMLOnEdit() ?
1339 $this->getCanonicalParserOutput() : $this->getParserOutputForMetaData();
1340
1341 // Construct a LinksUpdate for the combined canonical output.
1342 $linksUpdate = new LinksUpdate(
1343 $title,
1344 $parserOutput,
1345 $recursive,
1346 // Redirect target may have changed if the page is or was a redirect.
1347 // (We can't check if it was definitely changed without additional queries.)
1348 $this->isRedirect() || $this->wasRedirect()
1349 );
1350 if ( $this->options['cause'] === PageLatestRevisionChangedEvent::CAUSE_MOVE ) {
1351 $linksUpdate->setMoveDetails( $this->options['oldtitle'] );
1352 }
1353
1354 $allUpdates[] = $linksUpdate;
1355 // NOTE: Run updates for all slots, not just the modified slots! Otherwise,
1356 // info for an inherited slot may end up being removed. This is also needed
1357 // to ensure that purges are effective.
1358 $renderedRevision = $this->getRenderedRevision();
1359
1360 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
1361 $slot = $this->getRawSlot( $role );
1362 $content = $slot->getContent();
1363 $handler = $content->getContentHandler();
1364
1365 $updates = $handler->getSecondaryDataUpdates(
1366 $title,
1367 $content,
1368 $role,
1369 $renderedRevision
1370 );
1371
1372 $allUpdates = array_merge( $allUpdates, $updates );
1373 }
1374
1375 // XXX: if a slot was removed by an earlier edit, but deletion updates failed to run at
1376 // that time, we don't know for which slots to run deletion updates when purging a page.
1377 // We'd have to examine the entire history of the page to determine that. Perhaps there
1378 // could be a "try extra hard" mode for that case that would run a DB query to find all
1379 // roles/models ever used on the page. On the other hand, removing slots should be quite
1380 // rare, so perhaps this isn't worth the trouble.
1381
1382 // TODO: consolidate with similar logic in WikiPage::getDeletionUpdates()
1383 $parentRevision = $this->getParentRevision();
1384 foreach ( $this->getRemovedSlotRoles() as $role ) {
1385 // HACK: we should get the content model of the removed slot from a SlotRoleHandler!
1386 // For now, find the slot in the parent revision - if the slot was removed, it should
1387 // always exist in the parent revision.
1388 $parentSlot = $parentRevision->getSlot( $role, RevisionRecord::RAW );
1389 $content = $parentSlot->getContent();
1390 $handler = $content->getContentHandler();
1391
1392 $updates = $handler->getDeletionUpdates(
1393 $title,
1394 $role
1395 );
1396
1397 $allUpdates = array_merge( $allUpdates, $updates );
1398 }
1399
1400 // TODO: hard deprecate SecondaryDataUpdates in favor of RevisionDataUpdates in 1.33!
1401 $this->hookRunner->onRevisionDataUpdates( $title, $renderedRevision, $allUpdates );
1402
1403 return $allUpdates;
1404 }
1405
1410 private function shouldGenerateHTMLOnEdit(): bool {
1411 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
1412 $slot = $this->getRawSlot( $role );
1413 $contentHandler = $this->contentHandlerFactory->getContentHandler( $slot->getModel() );
1414 if ( $contentHandler->generateHTMLOnEdit() ) {
1415 return true;
1416 }
1417 }
1418 return false;
1419 }
1420
1435 public function doUpdates() {
1436 $this->assertTransition( 'done' );
1437
1438 $this->emitEventsIfNeeded();
1439
1440 // TODO: move more logic into ingress objects subscribed to PageLatestRevisionChangedEvent!
1441 $event = $this->getPageLatestRevisionChangedEvent();
1442
1443 if ( $this->shouldGenerateHTMLOnEdit() ) {
1444 $this->triggerParserCacheUpdate();
1445 }
1446
1447 $this->doSecondaryDataUpdates( [
1448 // T52785 do not update any other pages on dummy revisions and null edits
1449 'recursive' => $event->isEffectiveContentChange(),
1450 // Defer the getCanonicalParserOutput() call made by getSecondaryDataUpdates()
1451 'defer' => DeferredUpdates::POSTSEND
1452 ] );
1453
1454 $id = $this->getPageId();
1455 $title = $this->getTitle();
1456 $wikiPage = $this->getWikiPage();
1457
1458 if ( !$title->exists() ) {
1459 wfDebug( __METHOD__ . ": Page doesn't exist any more, bailing out" );
1460
1461 $this->doTransition( 'done' );
1462 return;
1463 }
1464
1465 DeferredUpdates::addCallableUpdate( function () use ( $event ) {
1466 if (
1467 $this->options['oldcountable'] === 'no-change' ||
1468 ( !$event->isEffectiveContentChange()
1469 && !$event->hasCause( PageLatestRevisionChangedEvent::CAUSE_MOVE ) )
1470 ) {
1471 $good = 0;
1472 } elseif ( $event->isCreation() ) {
1473 $good = (int)$this->isCountable();
1474 } elseif ( $this->options['oldcountable'] !== null ) {
1475 $good = (int)$this->isCountable()
1476 - (int)$this->options['oldcountable'];
1477 } elseif ( isset( $this->pageState['oldCountable'] ) ) {
1478 $good = (int)$this->isCountable()
1479 - (int)$this->pageState['oldCountable'];
1480 } else {
1481 $good = 0;
1482 }
1483 $edits = $event->isEffectiveContentChange() ? 1 : 0;
1484 $pages = $event->isCreation() ? 1 : 0;
1485
1486 DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
1487 [ 'edits' => $edits, 'articles' => $good, 'pages' => $pages ]
1488 ) );
1489 } );
1490
1491 // TODO: move onArticleCreate and onArticleEdit into a PageEventEmitter service
1492 if ( $event->isCreation() ) {
1493 // Deferred update that adds a mw-recreated tag to edits that create new pages
1494 // which have an associated deletion log entry for the specific namespace/title combination
1495 // and which are not undeletes
1496 if ( !( $event->hasCause( PageLatestRevisionChangedEvent::CAUSE_UNDELETE ) ) ) {
1497 $revision = $this->revision;
1498 DeferredUpdates::addCallableUpdate( function () use ( $revision, $wikiPage ) {
1499 $this->maybeAddRecreateChangeTag( $wikiPage, $revision->getId() );
1500 } );
1501 }
1502 WikiPage::onArticleCreate( $title, $this->isRedirect() );
1503 } elseif ( $event->isEffectiveContentChange() ) { // T52785
1504 // TODO: Check $event->isNominalContentChange() instead so we still
1505 // trigger updates on null edits, but pass a flag to suppress
1506 // backlink purges through queueBacklinksJobs() id
1507 // $event->changedLatestRevisionId() returns false.
1508 WikiPage::onArticleEdit(
1509 $title,
1510 $this->revision,
1511 $this->getTouchedSlotRoles(),
1512 // Redirect target may have changed if the page is or was a redirect.
1513 // (We can't check if it was definitely changed without additional queries.)
1514 $this->isRedirect() || $this->wasRedirect()
1515 );
1516 }
1517
1518 if ( $event->hasCause( PageLatestRevisionChangedEvent::CAUSE_UNDELETE ) ) {
1519 $this->mainWANObjectCache->touchCheckKey(
1520 "DerivedPageDataUpdater:restore:page:$id"
1521 );
1522 }
1523
1524 $editResult = $event->getEditResult();
1525
1526 if ( $editResult && !$editResult->isNullEdit() ) {
1527 // Cache EditResult for future use, via
1528 // RevertTagUpdateManager::approveRevertedTagForRevision().
1529 // This drives RevertedTagUpdateManager::approveRevertedTagForRevision.
1530 // It is only needed if RCPatrolling is enabled and the edit is a revert.
1531 // Skip in other cases to avoid flooding the cache, see T386217 and T388573.
1532 if ( $editResult->isRevert() && $this->useRcPatrol ) {
1533 $this->editResultCache->set(
1534 $this->revision->getId(),
1535 $editResult
1536 );
1537 }
1538 }
1539
1540 $this->doTransition( 'done' );
1541 }
1542
1543 private function emitEventsIfNeeded(): void {
1544 if ( !$this->options['emitEvents'] ) {
1545 return;
1546 }
1547
1548 $this->emitEvents();
1549 }
1550
1554 public function emitEvents(): void {
1555 if ( !( $this->options['allowEvents'] ?? true ) ) {
1556 throw new LogicException( 'dispatchPageUpdatedEvent was disabled on this updater' );
1557 }
1558
1559 // don't dispatch again!
1560 $this->options['emitEvents'] = false;
1561 $this->options['allowEvents'] = false;
1562
1563 $pageLatestRevisionChangedEvent = $this->getPageLatestRevisionChangedEvent();
1564 $pageCreatedEvent = $this->getPageCreatedEvent();
1565
1566 if (
1567 $pageLatestRevisionChangedEvent->getPageRecordBefore() === null &&
1568 !$this->options['created']
1569 ) {
1570 // if the page wasn't just created, we need the state before
1571 throw new LogicException( 'Missing page state before update' );
1572 }
1573
1574 $this->eventDispatcher->dispatch(
1575 $pageLatestRevisionChangedEvent,
1576 $this->loadbalancerFactory
1577 );
1578
1579 if ( $pageCreatedEvent ) {
1580 // NOTE: Emit PageCreated after PageLatestRevisionChanged, because the creation
1581 // is only finished after the revision has been set.
1582 $this->eventDispatcher->dispatch( $pageCreatedEvent, $this->loadbalancerFactory );
1583 }
1584 }
1585
1586 private function getNominalPerformer(): UserIdentity {
1588 $performer = $this->options['triggeringUser'] ?? $this->user;
1589 '@phan-var UserIdentity $performer';
1590
1591 return $performer;
1592 }
1593
1594 private function getPageLatestRevisionChangedEvent(): PageLatestRevisionChangedEvent {
1595 if ( $this->pageLatestRevisionChangedEvent ) {
1596 return $this->pageLatestRevisionChangedEvent;
1597 }
1598
1599 $this->assertHasRevision( __METHOD__ );
1600
1601 $flags = array_intersect_key(
1602 $this->options,
1603 PageLatestRevisionChangedEvent::DEFAULT_FLAGS
1604 );
1605
1606 $pageRecordBefore = $this->pageState['oldRecord'] ?? null;
1607 $pageRecordAfter = $this->getWikiPage()->toPageRecord();
1608
1609 $revisionBefore = $this->getOldRevision();
1610 $revisionAfter = $this->getRevision();
1611
1612 if ( $this->options['created'] ) {
1613 // Page creation. No prior state.
1614 // Force null to make sure we don't get confused during imports when
1615 // updates are triggered after importing the last revision of several.
1616 // In that case, the page and older revisions do already exist when
1617 // the DerivedPageDataUpdater is initialized, because they were
1618 // created during the import. But they didn't exist prior to the
1619 // import (based on the fact that the 'created' flag is set).
1620 $pageRecordBefore = null;
1621 $revisionBefore = null;
1622 } elseif ( !$this->options['changed'] ) {
1623 // Null edit. Should already be the same, just make sure.
1624 $pageRecordBefore = $pageRecordAfter;
1625 }
1626
1627 if ( $revisionBefore && $revisionAfter->getId() === $revisionBefore->getId() ) {
1628 // This is a null edit, flag it as a reconciliation request.
1629 $flags[ PageLatestRevisionChangedEvent::FLAG_RECONCILIATION_REQUEST ] = true;
1630 }
1631
1632 if ( $pageRecordBefore === null && !$this->options['created'] ) {
1633 // If the page wasn't just created, we need the state before.
1634 // If we are not actually emitting the event, we can ignore the issue.
1635 // This is needed to support the deprecated WikiPage::doEditUpdates()
1636 // method. Once that is gone, we can remove this conditional.
1637 if ( $this->options['emitEvents'] ) {
1638 throw new LogicException( 'Missing page state before update' );
1639 }
1640 }
1641
1642 $this->pageLatestRevisionChangedEvent = new PageLatestRevisionChangedEvent(
1643 $this->options['cause'] ?? PageUpdateCauses::CAUSE_EDIT,
1644 $pageRecordBefore,
1645 $pageRecordAfter,
1646 $revisionBefore,
1647 $revisionAfter,
1648 $this->getRevisionSlotsUpdate(),
1649 $this->options['editResult'] ?? null,
1650 $this->getNominalPerformer(),
1651 $this->options['tags'] ?? [],
1652 $flags,
1653 $this->options['rcPatrolStatus'] ?? 0,
1654 );
1655
1656 return $this->pageLatestRevisionChangedEvent;
1657 }
1658
1659 private function getPageCreatedEvent(): ?PageCreatedEvent {
1660 if ( !$this->options['created'] ) {
1661 return null;
1662 }
1663
1664 $pageRecordAfter = $this->getWikiPage()->toPageRecord();
1665
1666 return new PageCreatedEvent(
1667 $this->options['cause'] ?? PageUpdateCauses::CAUSE_EDIT,
1668 $pageRecordAfter,
1669 $this->getRevision(),
1670 $this->getNominalPerformer(),
1671 $this->options['reason'] ?? $this->getRevision()->getComment()->text,
1672 );
1673 }
1674
1675 private function triggerParserCacheUpdate() {
1676 $this->assertHasRevision( __METHOD__ );
1677
1678 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
1679 $userParserOptions = ParserOptions::newFromUser( $this->user );
1680
1681 // Decide whether to save the final canonical parser output based on the fact that
1682 // users are typically redirected to viewing pages right after they edit those pages.
1683 // Due to vary-revision-id, getting/saving that output here might require a reparse.
1684 if ( $userParserOptions->matchesForCacheKey( $this->getCanonicalParserOptions() ) ) {
1685 // Whether getting the final output requires a reparse or not, the user will
1686 // need canonical output anyway, since that is what their parser options use.
1687 // A reparse now at least has the benefit of various warm process caches.
1688 $this->doParserCacheUpdate();
1689 } else {
1690 // If the user does not have canonical parse options, then don't risk another parse
1691 // to make output they cannot use on the page refresh that typically occurs after
1692 // editing. Doing the parser output save post-send will still benefit *other* users.
1693 DeferredUpdates::addCallableUpdate( function () {
1694 $this->doParserCacheUpdate();
1695 } );
1696 }
1697 }
1698
1707 private function maybeAddRecreateChangeTag( WikiPage $wikiPage, int $revisionId ) {
1708 $dbr = $this->loadbalancerFactory->getReplicaDatabase();
1709
1710 if ( $dbr->newSelectQueryBuilder()
1711 ->select( [ '1' ] )
1712 ->from( 'logging' )
1713 ->where( [
1714 'log_type' => 'delete',
1715 'log_title' => $wikiPage->getTitle()->getDBkey(),
1716 'log_namespace' => $wikiPage->getNamespace(),
1717 ] )
1718 ->where(
1719 $dbr->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) .
1720 ' != ' . LogPage::DELETED_ACTION // T385792
1721 )->caller( __METHOD__ )->limit( 1 )->fetchField() ) {
1722 $this->changeTagsStore->addTags(
1723 [ ChangeTags::TAG_RECREATE ],
1724 null,
1725 $revisionId );
1726 }
1727 }
1728
1744 public function doSecondaryDataUpdates( array $options = [] ) {
1745 $this->assertHasRevision( __METHOD__ );
1746 $options += [ 'recursive' => false, 'defer' => false, 'freshness' => false ];
1747 $deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
1748 if ( !in_array( $options['defer'], $deferValues, true ) ) {
1749 throw new InvalidArgumentException( 'Invalid value for defer: ' . $options['defer'] );
1750 }
1751
1752 $triggeringUser = $this->options['triggeringUser'] ?? $this->user;
1753 [ $causeAction, $causeAgent ] = $this->getCauseForTracing();
1754 if ( isset( $options['known-revision-output'] ) ) {
1755 $this->getRenderedRevision()->setRevisionParserOutput( $options['known-revision-output'] );
1756 }
1757
1758 // Bundle all of the data updates into a single deferred update wrapper so that
1759 // any failure will cause at most one refreshLinks job to be enqueued by
1760 // DeferredUpdates::doUpdates(). This is hard to do when there are many separate
1761 // updates that are not defined as being related.
1762 $update = new RefreshSecondaryDataUpdate(
1763 $this->loadbalancerFactory,
1764 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Already checked
1765 $triggeringUser,
1766 $this->wikiPage,
1767 $this->revision,
1768 $this,
1769 [ 'recursive' => $options['recursive'], 'freshness' => $options['freshness'] ]
1770 );
1771 $update->setCause( $causeAction, $causeAgent );
1772
1773 if ( $options['defer'] === false ) {
1774 DeferredUpdates::attemptUpdate( $update );
1775 } else {
1776 DeferredUpdates::addUpdate( $update, $options['defer'] );
1777 }
1778 }
1779
1786 public function doParserCacheUpdate() {
1787 $this->assertHasRevision( __METHOD__ );
1788
1789 $wikiPage = $this->getWikiPage(); // TODO: ParserCache should accept a RevisionRecord instead
1790
1791 // NOTE: this may trigger the first parsing of the new content after an edit (when not
1792 // using pre-generated stashed output).
1793 // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse
1794 // to be performed post-send. The client could already follow a HTTP redirect to the
1795 // page view, but would then have to wait for a response until rendering is complete.
1796 $output = $this->getCanonicalParserOutput();
1797
1798 // Save it to the parser cache. Use the revision timestamp in the case of a
1799 // freshly saved edit, as that matches page_touched and a mismatch would trigger an
1800 // unnecessary reparse.
1801 $timestamp = $this->options['newrev'] ? $this->revision->getTimestamp()
1802 : $output->getCacheTime();
1803 $this->parserCache->save(
1804 $output, $wikiPage, $this->getCanonicalParserOptions(),
1805 $timestamp, $this->revision->getId()
1806 );
1807
1808 // If we enable cache warming with parsoid outputs, let's do it at the same
1809 // time we're populating the parser cache with pre-generated HTML.
1810 // Use OPT_FORCE_PARSE to avoid a useless cache lookup.
1811 if ( $this->warmParsoidParserCache ) {
1812 $cacheWarmingParams = $this->getCauseForTracing();
1813 $cacheWarmingParams['options'] = ParserOutputAccess::OPT_FORCE_PARSE;
1814
1815 $this->jobQueueGroup->lazyPush(
1816 ParsoidCachePrewarmJob::newSpec(
1817 $this->revision->getId(),
1818 $wikiPage->toPageRecord(),
1819 $cacheWarmingParams
1820 )
1821 );
1822 }
1823 }
1824
1825}
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:69
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:65
Class to simplify the use of log pages.
Definition LogPage.php:34
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:83
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.
getSlot( $role, $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Returns meta-data for the given slot.
getUser( $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Fetch revision's author's user identity, if it's available to the specified audience.
getSlotRoles()
Returns the slot names (roles) of all slots present in this revision.
getParentId( $wikiId=self::LOCAL)
Get parent revision ID (the original previous page revision).
getVisibility()
Get the deletion bitfield of the revision.
getMainContentRaw()
Returns the Content of the main slot of this revision.
getPageId( $wikiId=self::LOCAL)
Get the page ID.
getComment( $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Fetch revision comment, if it's available to the specified audience.
getSlots()
Returns the slots defined for this revision.
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
isMinor()
MCR migration note: this replaced Revision::isMinor.
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.
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.
grabLatestRevision()
Returns the revision that was the page's latest revision when grabLatestRevision() was first called.
__construct(ServiceOptions $options, PageIdentity $page, private readonly RevisionStore $revisionStore, private readonly RevisionRenderer $revisionRenderer, private readonly SlotRoleRegistry $slotRoleRegistry, private readonly ParserCache $parserCache, private readonly JobQueueGroup $jobQueueGroup, private readonly Language $contLang, private readonly ILBFactory $loadbalancerFactory, private readonly IContentHandlerFactory $contentHandlerFactory, HookContainer $hookContainer, private readonly DomainEventDispatcher $eventDispatcher, private readonly EditResultCache $editResultCache, private readonly ContentTransformer $contentTransformer, private readonly PageEditStash $pageEditStash, private readonly WANObjectCache $mainWANObjectCache, WikiPageFactory $wikiPageFactory, private readonly ChangeTagsStore $changeTagsStore,)
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 latest revision after the edit is different from the content of the latest...
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(),...
hasSameUpdates(RevisionSlotsUpdate $other)
Returns true if $other represents the same update - that is, if all methods defined by RevisionSlotsU...
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'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Content objects represent page content, e.g.
Definition Content.php:28
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.