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 public const array CONSTRUCTOR_OPTIONS = [
97 ];
98
99 private ?UserIdentity $user = null;
100 private readonly WikiPage $wikiPage;
101 private readonly HookRunner $hookRunner;
102 private LoggerInterface $logger;
103
111 private array $options = [
112 'changed' => true,
113 // newrev is true if prepareUpdate is handling the creation of a new revision,
114 // as opposed to a null edit or a forced update.
115 'newrev' => false,
116 'created' => false,
117 'oldtitle' => null,
118 'oldrevision' => null,
119 'oldcountable' => null,
120 'oldredirect' => null,
121 'triggeringUser' => null,
122 // causeAction/causeAgent default to 'unknown' but that's handled where it's read,
123 // to make the life of prepareUpdate() callers easier.
124 'causeAction' => null,
125 'causeAgent' => null,
126 'editResult' => null,
127 'rcPatrolStatus' => 0,
128 'tags' => [],
129 'cause' => 'edit',
130 'reason' => null,
131 'emitEvents' => true,
132 ] + PageLatestRevisionChangedEvent::DEFAULT_FLAGS;
133
155 private $pageState = null;
156 private ?RevisionSlotsUpdate $slotsUpdate = null;
157 private ?RevisionRecord $parentRevision = null;
158 private ?RevisionRecord $revision = null;
159 private ?RenderedRevision $renderedRevision = null;
160 private ?PageLatestRevisionChangedEvent $pageLatestRevisionChangedEvent = null;
161
165 private bool $forceEmptyRevision = false;
166
173 private string $stage = 'new';
174
183 private const TRANSITIONS = [
184 'new' => [
185 'new' => true,
186 'knows-current' => true,
187 'has-content' => true,
188 'has-revision' => true,
189 ],
190 'knows-current' => [
191 'knows-current' => true,
192 'has-content' => true,
193 'has-revision' => true,
194 ],
195 'has-content' => [
196 'has-content' => true,
197 'has-revision' => true,
198 ],
199 'has-revision' => [
200 'has-revision' => true,
201 'done' => true,
202 ],
203 ];
204
205 public function __construct(
206 private readonly ServiceOptions $serviceOptions,
207 PageIdentity $page,
208 private readonly RevisionStore $revisionStore,
209 private readonly RevisionRenderer $revisionRenderer,
210 private readonly SlotRoleRegistry $slotRoleRegistry,
211 private readonly ParserCache $parserCache,
212 private readonly JobQueueGroup $jobQueueGroup,
213 private readonly Language $contLang,
214 private readonly ILBFactory $loadbalancerFactory,
215 private readonly IContentHandlerFactory $contentHandlerFactory,
216 HookContainer $hookContainer,
217 private readonly DomainEventDispatcher $eventDispatcher,
218 private readonly EditResultCache $editResultCache,
219 private readonly ContentTransformer $contentTransformer,
220 private readonly PageEditStash $pageEditStash,
221 private readonly WANObjectCache $mainWANObjectCache,
222 WikiPageFactory $wikiPageFactory,
223 private readonly ChangeTagsStore $changeTagsStore,
224 ) {
225 $this->serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
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
235 public function setLogger( LoggerInterface $logger ): void {
236 $this->logger = $logger;
237 }
238
247 public function setCause( string $cause ) {
248 // 'cause' is for use in PageLatestRevisionChangedEvent, 'causeAction' is for
249 // use in tracing in updates, jobs, and RevisionRenderer.
250 // Note that PageLatestRevisionChangedEvent uses causes like "edit" and "move", but
251 // the convention for causeAction is to use "page-edit", etc.
252 $this->options['cause'] = $cause;
253 $this->options['causeAction'] = 'page-' . $cause;
254 }
255
261 public function setPerformer( UserIdentity $performer ) {
262 $this->options['triggeringUser'] = $performer;
263 $this->options['causeAgent'] = $performer->getName();
264 }
265
269 private function getCauseForTracing(): array {
270 return [
271 $this->options['causeAction'] ?? 'unknown',
272 $this->options['causeAgent']
273 ?? ( $this->user ? $this->user->getName() : 'unknown' ),
274 ];
275 }
276
285 private function doTransition( $newStage ) {
286 $this->assertTransition( $newStage );
287
288 $oldStage = $this->stage;
289 $this->stage = $newStage;
290
291 return $oldStage;
292 }
293
301 private function assertTransition( $newStage ) {
302 if ( empty( self::TRANSITIONS[$this->stage][$newStage] ) ) {
303 throw new LogicException( "Cannot transition from {$this->stage} to $newStage" );
304 }
305 }
306
318 public function isReusableFor(
319 ?UserIdentity $user = null,
320 ?RevisionRecord $revision = null,
321 ?RevisionSlotsUpdate $slotsUpdate = null,
322 $parentId = null
323 ) {
324 if ( $revision
325 && $parentId
326 && $revision->getParentId() !== $parentId
327 ) {
328 throw new InvalidArgumentException( '$parentId should match the parent of $revision' );
329 }
330
331 // NOTE: For dummy revisions, $user may be different from $this->revision->getUser
332 // and also from $revision->getUser.
333 // But $user should always match $this->user.
334 if ( $user && $this->user && $user->getName() !== $this->user->getName() ) {
335 return false;
336 }
337
338 if ( $revision && $this->revision && $this->revision->getId()
339 && $this->revision->getId() !== $revision->getId()
340 ) {
341 return false;
342 }
343
344 if ( $this->pageState
345 && $revision
346 && $revision->getParentId() !== null
347 && $this->pageState['oldId'] !== $revision->getParentId()
348 ) {
349 return false;
350 }
351
352 if ( $this->pageState
353 && $parentId !== null
354 && $this->pageState['oldId'] !== $parentId
355 ) {
356 return false;
357 }
358
359 // NOTE: this check is the primary reason for having the $this->slotsUpdate field!
360 if ( $this->slotsUpdate
361 && $slotsUpdate
362 && !$this->slotsUpdate->hasSameUpdates( $slotsUpdate )
363 ) {
364 return false;
365 }
366
367 if ( $revision
368 && $this->revision
369 && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
370 ) {
371 return false;
372 }
373
374 return true;
375 }
376
388 public function setForceEmptyRevision( bool $forceEmptyRevision ) {
389 if ( $this->revision ) {
390 throw new LogicException( 'prepareContent() or prepareUpdate() was already called.' );
391 }
392
393 $this->forceEmptyRevision = $forceEmptyRevision;
394 }
395
399 private function getTitle() {
400 // NOTE: eventually, this won't use WikiPage any more
401 return $this->wikiPage->getTitle();
402 }
403
407 private function getWikiPage() {
408 // NOTE: eventually, this won't use WikiPage any more
409 return $this->wikiPage;
410 }
411
417 public function getPage(): ProperPageIdentity {
418 return $this->wikiPage;
419 }
420
428 public function pageExisted() {
429 $this->assertHasPageState( __METHOD__ );
430
431 return $this->pageState['oldId'] > 0;
432 }
433
443 private function getParentRevision() {
444 $this->assertPrepared( __METHOD__ );
445
446 if ( $this->parentRevision ) {
447 return $this->parentRevision;
448 }
449
450 if ( !$this->pageState['oldId'] ) {
451 // If there was no latest revision, there is no parent revision,
452 // since the page didn't exist.
453 return null;
454 }
455
456 $oldId = $this->revision->getParentId();
457 $flags = $this->usePrimary() ? IDBAccessObject::READ_LATEST : 0;
458 $this->parentRevision = $oldId
459 ? $this->revisionStore->getRevisionById( $oldId, $flags )
460 : null;
461
462 return $this->parentRevision;
463 }
464
472 private function getOldRevision() {
473 $this->assertPrepared( __METHOD__ );
474 return $this->pageState['oldRevision'];
475 }
476
498 public function grabLatestRevision() {
499 if ( $this->pageState ) {
500 return $this->pageState['oldRevision'];
501 }
502
503 $this->assertTransition( 'knows-current' );
504
505 // NOTE: eventually, this won't use WikiPage any more
506 $wikiPage = $this->getWikiPage();
507
508 // Do not call WikiPage::clear(), since the caller may already have caused page data
509 // to be loaded with SELECT FOR UPDATE. Just assert it's loaded now.
510 $wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
511 $current = $wikiPage->getRevisionRecord();
512
513 $this->pageState = [
514 'oldRevision' => $current,
515 'oldId' => $current ? $current->getId() : 0,
516 'oldIsRedirect' => $wikiPage->isRedirect(), // NOTE: uses page table
517 'oldCountable' => $wikiPage->isCountable(), // NOTE: uses pagelinks table
518 'oldRecord' => $wikiPage->exists() ? $wikiPage->toPageRecord() : null,
519 ];
520
521 $this->doTransition( 'knows-current' );
522
523 return $this->pageState['oldRevision'];
524 }
525
530 public function grabCurrentRevision() {
531 return $this->grabLatestRevision();
532 }
533
539 public function isContentPrepared() {
540 return $this->revision !== null;
541 }
542
550 public function isUpdatePrepared() {
551 return $this->revision !== null && $this->revision->getId() !== null;
552 }
553
557 private function getPageId() {
558 // NOTE: eventually, this won't use WikiPage any more
559 return $this->wikiPage->getId();
560 }
561
567 public function isContentDeleted() {
568 if ( $this->revision ) {
569 return $this->revision->isDeleted( RevisionRecord::DELETED_TEXT );
570 } else {
571 // If the content has not been saved yet, it cannot have been deleted yet.
572 return false;
573 }
574 }
575
585 public function getRawSlot( $role ) {
586 return $this->getSlots()->getSlot( $role );
587 }
588
597 public function getRawContent( string $role ): Content {
598 return $this->getRawSlot( $role )->getContent();
599 }
600
601 private function usePrimary(): bool {
602 // TODO: can we just set a flag to true in prepareContent()?
603 return $this->wikiPage->wasLoadedFrom( IDBAccessObject::READ_LATEST );
604 }
605
606 public function isCountable(): bool {
607 // NOTE: Keep in sync with WikiPage::isCountable.
608
609 if ( !$this->getTitle()->isContentPage() ) {
610 return false;
611 }
612
613 if ( $this->isContentDeleted() ) {
614 // This should be irrelevant: countability only applies to the latest revision,
615 // and the latest revision is never suppressed.
616 return false;
617 }
618
619 if ( $this->isRedirect() ) {
620 return false;
621 }
622
623 $hasLinks = null;
624
625 if ( $this->serviceOptions->get( MainConfigNames::ArticleCountMethod ) === 'link' ) {
626 // NOTE: it would be more appropriate to determine for each slot separately
627 // whether it has links, and use that information with that slot's
628 // isCountable() method. However, that would break parity with
629 // WikiPage::isCountable, which uses the pagelinks table to determine
630 // whether the latest revision has links.
631 $hasLinks = $this->getParserOutputForMetaData()->hasLinks();
632 }
633
634 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
635 $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
636 if ( $roleHandler->supportsArticleCount() ) {
637 $content = $this->getRawContent( $role );
638
639 if ( $content->isCountable( $hasLinks ) ) {
640 return true;
641 }
642 }
643 }
644
645 return false;
646 }
647
648 public function isRedirect(): bool {
649 // NOTE: main slot determines redirect status
650 // TODO: MCR: this should be controlled by a PageTypeHandler
651 $mainContent = $this->getRawContent( SlotRecord::MAIN );
652
653 return $mainContent->isRedirect();
654 }
655
661 private function revisionIsRedirect( RevisionRecord $rev ) {
662 // NOTE: main slot determines redirect status
663 $mainContent = $rev->getMainContentRaw();
664
665 return $mainContent->isRedirect();
666 }
667
691 public function prepareContent(
692 UserIdentity $user,
693 RevisionSlotsUpdate $slotsUpdate,
694 $useStash = true
695 ) {
696 if ( $this->slotsUpdate ) {
697 if ( !$this->user ) {
698 throw new LogicException(
699 'Unexpected state: $this->slotsUpdate was initialized, '
700 . 'but $this->user was not.'
701 );
702 }
703
704 if ( $this->user->getName() !== $user->getName() ) {
705 throw new LogicException( 'Can\'t call prepareContent() again for different user! '
706 . 'Expected ' . $this->user->getName() . ', got ' . $user->getName()
707 );
708 }
709
710 if ( !$this->slotsUpdate->hasSameUpdates( $slotsUpdate ) ) {
711 throw new LogicException(
712 'Can\'t call prepareContent() again with different slot content!'
713 );
714 }
715
716 return; // prepareContent() already done, nothing to do
717 }
718
719 $this->assertTransition( 'has-content' );
720
721 $wikiPage = $this->getWikiPage(); // TODO: use only for legacy hooks!
722 $title = $this->getTitle();
723
724 $parentRevision = $this->grabLatestRevision();
725
726 // The edit may have already been prepared via api.php?action=stashedit
727 $stashedEdit = false;
728
729 // TODO: MCR: allow output for all slots to be stashed.
730 if ( $useStash && $slotsUpdate->isModifiedSlot( SlotRecord::MAIN ) ) {
731 $stashedEdit = $this->pageEditStash->checkCache(
732 $title,
733 $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent(),
734 $user
735 );
736 }
737
738 $userPopts = ParserOptions::newFromUserAndLang( $user, $this->contLang );
739 $userPopts->setRenderReason( $this->options['causeAgent'] ?? 'unknown' );
740
741 $this->hookRunner->onArticlePrepareTextForEdit( $wikiPage, $userPopts );
742
743 $this->user = $user;
744 $this->slotsUpdate = $slotsUpdate;
745
746 if ( $parentRevision ) {
747 $this->revision = MutableRevisionRecord::newFromParentRevision( $parentRevision );
748 } else {
749 $this->revision = new MutableRevisionRecord( $title );
750 }
751
752 // NOTE: user and timestamp must be set, so they can be used for
753 // {{subst:REVISIONUSER}} and {{subst:REVISIONTIMESTAMP}} in PST!
754 $this->revision->setTimestamp( MWTimestamp::now( TS::MW ) );
755 $this->revision->setUser( $user );
756
757 // Set up ParserOptions to operate on the new revision
758 $oldCallback = $userPopts->getCurrentRevisionRecordCallback();
759 $userPopts->setCurrentRevisionRecordCallback(
760 function ( Title $parserTitle, $parser = null ) use ( $title, $oldCallback ) {
761 if ( $parserTitle->equals( $title ) ) {
762 return $this->revision;
763 } else {
764 return $oldCallback( $parserTitle, $parser );
765 }
766 }
767 );
768
769 $pstContentSlots = $this->revision->getSlots();
770
771 foreach ( $slotsUpdate->getModifiedRoles() as $role ) {
772 $slot = $slotsUpdate->getModifiedSlot( $role );
773
774 if ( $slot->isInherited() ) {
775 // No PST for inherited slots! Note that "modified" slots may still be inherited
776 // from an earlier version, e.g. for rollbacks.
777 $pstSlot = $slot;
778 } elseif ( $role === SlotRecord::MAIN && $stashedEdit ) {
779 // TODO: MCR: allow PST content for all slots to be stashed.
780 $pstSlot = SlotRecord::newUnsaved( $role, $stashedEdit->pstContent );
781 } else {
782 $pstContent = $this->contentTransformer->preSaveTransform(
783 $slot->getContent(),
784 $title,
785 $user,
786 $userPopts
787 );
788
789 $pstSlot = SlotRecord::newUnsaved( $role, $pstContent );
790 }
791
792 $pstContentSlots->setSlot( $pstSlot );
793 }
794
795 foreach ( $slotsUpdate->getRemovedRoles() as $role ) {
796 $pstContentSlots->removeSlot( $role );
797 }
798
799 $this->options['created'] = ( $parentRevision === null );
800 $this->options['changed'] = ( $parentRevision === null
801 || !$pstContentSlots->hasSameContent( $parentRevision->getSlots() ) );
802
803 $this->doTransition( 'has-content' );
804
805 if ( !$this->options['changed'] ) {
806 if ( $this->forceEmptyRevision ) {
807 // dummy revision, inherit all slots
808 foreach ( $parentRevision->getSlotRoles() as $role ) {
809 $this->revision->inheritSlot( $parentRevision->getSlot( $role ) );
810 }
811 } else {
812 // null-edit, the new revision *is* the old revision.
813
814 // TODO: move this into MutableRevisionRecord
815 $this->revision->setId( $parentRevision->getId() );
816 $this->revision->setTimestamp( $parentRevision->getTimestamp() );
817 $this->revision->setPageId( $parentRevision->getPageId() );
818 $this->revision->setParentId( $parentRevision->getParentId() );
819 $this->revision->setUser( $parentRevision->getUser( RevisionRecord::RAW ) );
820 $this->revision->setComment( $parentRevision->getComment( RevisionRecord::RAW ) );
821 $this->revision->setMinorEdit( $parentRevision->isMinor() );
822 $this->revision->setVisibility( $parentRevision->getVisibility() );
823
824 // prepareUpdate() is redundant for null-edits (but not for dummy revisions)
825 $this->doTransition( 'has-revision' );
826 }
827 } else {
828 $this->parentRevision = $parentRevision;
829 }
830
831 $renderHints = [ 'use-master' => $this->usePrimary(), 'audience' => RevisionRecord::RAW ];
832
833 if ( $stashedEdit ) {
835 $output = $stashedEdit->output;
836 // TODO: this should happen when stashing the ParserOutput, not now!
837 $output->setCacheTime( $stashedEdit->timestamp );
838
839 $renderHints['known-revision-output'] = $output;
840
841 $this->logger->debug( __METHOD__ . ': using stashed edit output...' );
842 }
843
844 $renderHints['generate-html'] = $this->shouldGenerateHTMLOnEdit();
845
846 [ $causeAction, ] = $this->getCauseForTracing();
847 $renderHints['causeAction'] = $causeAction;
848
849 // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
850 // NOTE: the revision is either new or current, so we can bypass audience checks.
851 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
852 $this->revision,
853 null,
854 null,
855 $renderHints
856 );
857 }
858
874 public function getRevision(): RevisionRecord {
875 $this->assertPrepared( __METHOD__ );
876 return $this->revision;
877 }
878
880 $this->assertPrepared( __METHOD__ );
881
882 return $this->renderedRevision;
883 }
884
885 private function assertHasPageState( string $method ) {
886 if ( !$this->pageState ) {
887 throw new LogicException(
888 'Must call grabLatestRevision() or prepareContent() '
889 . 'or prepareUpdate() before calling ' . $method
890 );
891 }
892 }
893
894 private function assertPrepared( string $method ) {
895 if ( !$this->revision ) {
896 throw new LogicException(
897 'Must call prepareContent() or prepareUpdate() before calling ' . $method
898 );
899 }
900 }
901
902 private function assertHasRevision( string $method ) {
903 if ( !$this->revision->getId() ) {
904 throw new LogicException(
905 'Must call prepareUpdate() before calling ' . $method
906 );
907 }
908 }
909
915 public function isCreation() {
916 $this->assertPrepared( __METHOD__ );
917 return $this->options['created'];
918 }
919
932 public function isChange() {
933 $this->assertPrepared( __METHOD__ );
934 return $this->options['changed'];
935 }
936
942 public function wasRedirect() {
943 $this->assertHasPageState( __METHOD__ );
944
945 if ( $this->pageState['oldIsRedirect'] === null ) {
947 $rev = $this->pageState['oldRevision'];
948 if ( $rev ) {
949 $this->pageState['oldIsRedirect'] = $this->revisionIsRedirect( $rev );
950 } else {
951 $this->pageState['oldIsRedirect'] = false;
952 }
953 }
954
955 return $this->pageState['oldIsRedirect'];
956 }
957
966 public function getSlots() {
967 $this->assertPrepared( __METHOD__ );
968 return $this->revision->getSlots();
969 }
970
976 private function getRevisionSlotsUpdate() {
977 $this->assertPrepared( __METHOD__ );
978
979 if ( !$this->slotsUpdate ) {
980 $old = $this->getParentRevision();
981 $this->slotsUpdate = RevisionSlotsUpdate::newFromRevisionSlots(
982 $this->revision->getSlots(),
983 $old ? $old->getSlots() : null
984 );
985 }
986 return $this->slotsUpdate;
987 }
988
995 public function getTouchedSlotRoles() {
996 return $this->getRevisionSlotsUpdate()->getTouchedRoles();
997 }
998
1005 public function getModifiedSlotRoles(): array {
1006 return $this->getRevisionSlotsUpdate()->getModifiedRoles();
1007 }
1008
1014 public function getRemovedSlotRoles(): array {
1015 return $this->getRevisionSlotsUpdate()->getRemovedRoles();
1016 }
1017
1063 public function prepareUpdate( RevisionRecord $revision, array $options = [] ) {
1064 Assert::parameter(
1065 !isset( $options['oldrevision'] )
1066 || $options['oldrevision'] instanceof RevisionRecord,
1067 '$options["oldrevision"]',
1068 'must be a RevisionRecord'
1069 );
1070 Assert::parameter(
1071 !isset( $options['triggeringUser'] )
1072 || $options['triggeringUser'] instanceof UserIdentity,
1073 '$options["triggeringUser"]',
1074 'must be a UserIdentity'
1075 );
1076 Assert::parameter(
1077 !isset( $options['editResult'] )
1078 || $options['editResult'] instanceof EditResult,
1079 '$options["editResult"]',
1080 'must be an EditResult'
1081 );
1082
1083 if ( !$revision->getId() ) {
1084 throw new InvalidArgumentException(
1085 'Revision must have an ID set for it to be used with prepareUpdate()!'
1086 );
1087 }
1088
1089 if ( !$this->wikiPage->exists() ) {
1090 // If the ongoing edit is creating the page, the state of $this->wikiPage
1091 // may be out of whack. This would only happen if the page creation was
1092 // done using a different WikiPage instance, which shouldn't be the case.
1093 $this->logger->warning(
1094 __METHOD__ . ': Reloading page meta-data after page creation',
1095 [
1096 'page' => (string)$this->wikiPage,
1097 'rev_id' => $revision->getId(),
1098 ]
1099 );
1100
1101 $this->wikiPage->clear();
1102 $this->wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
1103 }
1104
1105 if ( $this->revision && $this->revision->getId() ) {
1106 if ( $this->revision->getId() === $revision->getId() ) {
1107 $this->options['changed'] = false; // null-edit
1108 } else {
1109 throw new LogicException(
1110 'Trying to re-use DerivedPageDataUpdater with revision '
1111 . $revision->getId()
1112 . ', but it\'s already bound to revision '
1113 . $this->revision->getId()
1114 );
1115 }
1116 }
1117
1118 if ( $this->revision
1119 && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
1120 ) {
1121 throw new LogicException(
1122 'The revision provided has mismatching content!'
1123 );
1124 }
1125
1126 // Override fields defined in $this->options with values from $options.
1127 $this->options = array_intersect_key( $options, $this->options ) + $this->options;
1128
1129 if ( $this->revision ) {
1130 $oldId = $this->pageState['oldId'] ?? 0;
1131 $this->options['newrev'] = ( $revision->getId() !== $oldId );
1132 } elseif ( isset( $this->options['oldrevision'] ) ) {
1134 $oldRev = $this->options['oldrevision'];
1135 $oldId = $oldRev->getId();
1136 $this->options['newrev'] = ( $revision->getId() !== $oldId );
1137 } else {
1138 $oldId = $revision->getParentId();
1139 }
1140
1141 if ( $oldId !== null ) {
1142 // XXX: what if $options['changed'] disagrees?
1143 // MovePage creates a dummy revision with changed = false!
1144 // We may want to explicitly distinguish between "no new revision" (null-edit)
1145 // and "new revision without new content" (dummy revision).
1146
1147 if ( $oldId === $revision->getParentId() ) {
1148 // NOTE: this may still be a dummy revision!
1149 // New revision!
1150 $this->options['changed'] = true;
1151 } elseif ( $oldId === $revision->getId() ) {
1152 // Null-edit!
1153 $this->options['changed'] = false;
1154 } else {
1155 // This indicates that calling code has given us the wrong RevisionRecord object
1156 throw new LogicException(
1157 'The RevisionRecord mismatches old revision ID: '
1158 . 'Old ID is ' . $oldId
1159 . ', parent ID is ' . $revision->getParentId()
1160 . ', revision ID is ' . $revision->getId()
1161 );
1162 }
1163 }
1164
1165 // If prepareContent() was used to generate the PST content (which is indicated by
1166 // $this->slotsUpdate being set), and this is not a null-edit, then the given
1167 // revision must have the acting user as the revision author. Otherwise, user
1168 // signatures generated by PST would mismatch the user in the revision record.
1169 if ( $this->user !== null && $this->options['changed'] && $this->slotsUpdate ) {
1170 $user = $revision->getUser();
1171 if ( !$this->user->equals( $user ) ) {
1172 throw new LogicException(
1173 'The RevisionRecord provided has a mismatching actor: expected '
1174 . $this->user->getName()
1175 . ', got '
1176 . $user->getName()
1177 );
1178 }
1179 }
1180
1181 // If $this->pageState was not yet initialized by grabLatestRevision() or prepareContent(),
1182 // emulate the state of the page table before the edit, as good as we can.
1183 if ( !$this->pageState ) {
1184 $this->pageState = [
1185 'oldIsRedirect' => isset( $this->options['oldredirect'] )
1186 && is_bool( $this->options['oldredirect'] )
1187 ? $this->options['oldredirect']
1188 : null,
1189 'oldCountable' => isset( $this->options['oldcountable'] )
1190 && is_bool( $this->options['oldcountable'] )
1191 ? $this->options['oldcountable']
1192 : null,
1193 ];
1194
1195 if ( $this->options['changed'] ) {
1196 // The edit created a new revision
1197 $this->pageState['oldId'] = $revision->getParentId();
1198 // Old revision is null if this is a page creation
1199 $this->pageState['oldRevision'] = $this->options['oldrevision'] ?? null;
1200 } else {
1201 // This is a null-edit, so the old revision IS the new revision!
1202 $this->pageState['oldId'] = $revision->getId();
1203 $this->pageState['oldRevision'] = $revision;
1204 }
1205 }
1206
1207 // "created" is forced here
1208 $this->options['created'] = ( $this->options['created'] ||
1209 ( $this->pageState['oldId'] === 0 ) );
1210
1211 $this->revision = $revision;
1212
1213 $this->doTransition( 'has-revision' );
1214
1215 // NOTE: in case we have a User object, don't override with a UserIdentity.
1216 // We already checked that $revision->getUser() matches $this->user;
1217 if ( !$this->user ) {
1218 $this->user = $revision->getUser( RevisionRecord::RAW );
1219 }
1220
1221 // Prune any output that depends on the revision ID.
1222 if ( $this->renderedRevision ) {
1223 $this->renderedRevision->updateRevision( $revision );
1224 } else {
1225 [ $causeAction, ] = $this->getCauseForTracing();
1226 // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
1227 // NOTE: the revision is either new or current, so we can bypass audience checks.
1228 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
1229 $this->revision,
1230 null,
1231 null,
1232 [
1233 'use-master' => $this->usePrimary(),
1234 'audience' => RevisionRecord::RAW,
1235 'known-revision-output' => $options['known-revision-output'] ?? null,
1236 'causeAction' => $causeAction
1237 ]
1238 );
1239
1240 // XXX: Since we presumably are dealing with the latest revision,
1241 // we could try to get the ParserOutput from the parser cache.
1242 }
1243
1244 // TODO: optionally get ParserOutput from the ParserCache here.
1245 // Move the logic used by RefreshLinksJob here!
1246 }
1247
1252 public function getPreparedEdit() {
1253 $this->assertPrepared( __METHOD__ );
1254
1255 $slotsUpdate = $this->getRevisionSlotsUpdate();
1256 $preparedEdit = new PreparedEdit();
1257
1258 $preparedEdit->popts = $this->getCanonicalParserOptions();
1259 $preparedEdit->parserOutputCallback = $this->getCanonicalParserOutput( ... );
1260 $preparedEdit->pstContent = $this->revision->getContent( SlotRecord::MAIN );
1261 $preparedEdit->newContent =
1262 $slotsUpdate->isModifiedSlot( SlotRecord::MAIN )
1263 ? $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent()
1264 : $this->revision->getContent( SlotRecord::MAIN ); // XXX: can we just remove this?
1265 $preparedEdit->oldContent = null; // unused. // XXX: could get this from the parent revision
1266 $preparedEdit->revid = $this->revision ? $this->revision->getId() : null;
1267 $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1268
1269 return $preparedEdit;
1270 }
1271
1277 public function getSlotParserOutput( $role, $generateHtml = true ) {
1278 return $this->getRenderedRevision()->getSlotParserOutput(
1279 $role,
1280 [ 'generate-html' => $generateHtml ]
1281 );
1282 }
1283
1289 return $this->getRenderedRevision()->getRevisionParserOutput( [ 'generate-html' => false ] );
1290 }
1291
1297 return $this->getRenderedRevision()->getRevisionParserOutput();
1298 }
1299
1301 return $this->getRenderedRevision()->getOptions();
1302 }
1303
1309 public function getSecondaryDataUpdates( $recursive = false ) {
1310 if ( $this->isContentDeleted() ) {
1311 // This shouldn't happen, since the current content is always public,
1312 // and DataUpdates are only needed for current content.
1313 return [];
1314 }
1315
1316 $wikiPage = $this->getWikiPage();
1317 $wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
1318 if ( !$wikiPage->exists() ) {
1319 // page deleted while deferring the update
1320 return [];
1321 }
1322
1323 $title = $wikiPage->getTitle();
1324 $allUpdates = [];
1325 $parserOutput = $this->shouldGenerateHTMLOnEdit() ?
1326 $this->getCanonicalParserOutput() : $this->getParserOutputForMetaData();
1327
1328 // Construct a LinksUpdate for the combined canonical output.
1329 $linksUpdate = new LinksUpdate(
1330 $title,
1331 $parserOutput,
1332 $recursive,
1333 // Redirect target may have changed if the page is or was a redirect.
1334 // (We can't check if it was definitely changed without additional queries.)
1335 $this->isRedirect() || $this->wasRedirect()
1336 );
1337 if ( $this->options['cause'] === PageLatestRevisionChangedEvent::CAUSE_MOVE ) {
1338 $linksUpdate->setMoveDetails( $this->options['oldtitle'] );
1339 }
1340
1341 $allUpdates[] = $linksUpdate;
1342 // NOTE: Run updates for all slots, not just the modified slots! Otherwise,
1343 // info for an inherited slot may end up being removed. This is also needed
1344 // to ensure that purges are effective.
1345 $renderedRevision = $this->getRenderedRevision();
1346
1347 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
1348 $slot = $this->getRawSlot( $role );
1349 $content = $slot->getContent();
1350 $handler = $content->getContentHandler();
1351
1352 $updates = $handler->getSecondaryDataUpdates(
1353 $title,
1354 $content,
1355 $role,
1356 $renderedRevision
1357 );
1358
1359 $allUpdates = array_merge( $allUpdates, $updates );
1360 }
1361
1362 // XXX: if a slot was removed by an earlier edit, but deletion updates failed to run at
1363 // that time, we don't know for which slots to run deletion updates when purging a page.
1364 // We'd have to examine the entire history of the page to determine that. Perhaps there
1365 // could be a "try extra hard" mode for that case that would run a DB query to find all
1366 // roles/models ever used on the page. On the other hand, removing slots should be quite
1367 // rare, so perhaps this isn't worth the trouble.
1368
1369 // TODO: consolidate with similar logic in WikiPage::getDeletionUpdates()
1370 $parentRevision = $this->getParentRevision();
1371 foreach ( $this->getRemovedSlotRoles() as $role ) {
1372 // HACK: we should get the content model of the removed slot from a SlotRoleHandler!
1373 // For now, find the slot in the parent revision - if the slot was removed, it should
1374 // always exist in the parent revision.
1375 $parentSlot = $parentRevision->getSlot( $role, RevisionRecord::RAW );
1376 $content = $parentSlot->getContent();
1377 $handler = $content->getContentHandler();
1378
1379 $updates = $handler->getDeletionUpdates(
1380 $title,
1381 $role
1382 );
1383
1384 $allUpdates = array_merge( $allUpdates, $updates );
1385 }
1386
1387 // TODO: hard deprecate SecondaryDataUpdates in favor of RevisionDataUpdates in 1.33!
1388 $this->hookRunner->onRevisionDataUpdates( $title, $renderedRevision, $allUpdates );
1389
1390 return $allUpdates;
1391 }
1392
1397 private function shouldGenerateHTMLOnEdit(): bool {
1398 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
1399 $slot = $this->getRawSlot( $role );
1400 $contentHandler = $this->contentHandlerFactory->getContentHandler( $slot->getModel() );
1401 if ( $contentHandler->generateHTMLOnEdit() ) {
1402 return true;
1403 }
1404 }
1405 return false;
1406 }
1407
1422 public function doUpdates() {
1423 $this->assertTransition( 'done' );
1424
1425 $this->emitEventsIfNeeded();
1426
1427 // TODO: move more logic into ingress objects subscribed to PageLatestRevisionChangedEvent!
1428 $event = $this->getPageLatestRevisionChangedEvent();
1429
1430 if ( $this->shouldGenerateHTMLOnEdit() ) {
1431 $this->triggerParserCacheUpdate();
1432 }
1433
1434 $this->doSecondaryDataUpdates( [
1435 // T52785 do not update any other pages on dummy revisions and null edits
1436 'recursive' => $event->isEffectiveContentChange(),
1437 // Defer the getCanonicalParserOutput() call made by getSecondaryDataUpdates()
1438 'defer' => DeferredUpdates::POSTSEND
1439 ] );
1440
1441 $id = $this->getPageId();
1442 $title = $this->getTitle();
1443 $wikiPage = $this->getWikiPage();
1444
1445 if ( !$title->exists() ) {
1446 wfDebug( __METHOD__ . ": Page doesn't exist any more, bailing out" );
1447
1448 $this->doTransition( 'done' );
1449 return;
1450 }
1451
1452 DeferredUpdates::addCallableUpdate( function () use ( $event ) {
1453 if (
1454 $this->options['oldcountable'] === 'no-change' ||
1455 ( !$event->isEffectiveContentChange()
1456 && !$event->hasCause( PageLatestRevisionChangedEvent::CAUSE_MOVE ) )
1457 ) {
1458 $good = 0;
1459 } elseif ( $event->isCreation() ) {
1460 $good = (int)$this->isCountable();
1461 } elseif ( $this->options['oldcountable'] !== null ) {
1462 $good = (int)$this->isCountable()
1463 - (int)$this->options['oldcountable'];
1464 } elseif ( isset( $this->pageState['oldCountable'] ) ) {
1465 $good = (int)$this->isCountable()
1466 - (int)$this->pageState['oldCountable'];
1467 } else {
1468 $good = 0;
1469 }
1470 $edits = $event->isEffectiveContentChange() ? 1 : 0;
1471 $pages = $event->isCreation() ? 1 : 0;
1472
1473 DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
1474 [ 'edits' => $edits, 'articles' => $good, 'pages' => $pages ]
1475 ) );
1476 } );
1477
1478 // TODO: move onArticleCreate and onArticleEdit into a PageEventEmitter service
1479 if ( $event->isCreation() ) {
1480 // Deferred update that adds a mw-recreated tag to edits that create new pages
1481 // which have an associated deletion log entry for the specific namespace/title combination
1482 // and which are not undeletes
1483 if ( !( $event->hasCause( PageLatestRevisionChangedEvent::CAUSE_UNDELETE ) ) ) {
1484 $revision = $this->revision;
1485 DeferredUpdates::addCallableUpdate( function () use ( $revision, $wikiPage ) {
1486 $this->maybeAddRecreateChangeTag( $wikiPage, $revision->getId() );
1487 } );
1488 }
1489 WikiPage::onArticleCreate( $title, $this->isRedirect() );
1490 } elseif ( $event->isEffectiveContentChange() ) { // T52785
1491 // TODO: Check $event->isNominalContentChange() instead so we still
1492 // trigger updates on null edits, but pass a flag to suppress
1493 // backlink purges through queueBacklinksJobs() id
1494 // $event->changedLatestRevisionId() returns false.
1495 WikiPage::onArticleEdit(
1496 $title,
1497 $this->revision,
1498 $this->getTouchedSlotRoles(),
1499 // Redirect target may have changed if the page is or was a redirect.
1500 // (We can't check if it was definitely changed without additional queries.)
1501 $this->isRedirect() || $this->wasRedirect()
1502 );
1503 }
1504
1505 if ( $event->hasCause( PageLatestRevisionChangedEvent::CAUSE_UNDELETE ) ) {
1506 $this->mainWANObjectCache->touchCheckKey(
1507 "DerivedPageDataUpdater:restore:page:$id"
1508 );
1509 }
1510
1511 $editResult = $event->getEditResult();
1512
1513 if ( $editResult && !$editResult->isNullEdit() ) {
1514 // Cache EditResult for future use, via
1515 // RevertTagUpdateManager::approveRevertedTagForRevision().
1516 // This drives RevertedTagUpdateManager::approveRevertedTagForRevision.
1517 // It is only needed if RCPatrolling is enabled and the edit is a revert.
1518 // Skip in other cases to avoid flooding the cache, see T386217 and T388573.
1519 if ( $editResult->isRevert() && $this->serviceOptions->get( MainConfigNames::UseRCPatrol ) ) {
1520 $this->editResultCache->set(
1521 $this->revision->getId(),
1522 $editResult
1523 );
1524 }
1525 }
1526
1527 $this->doTransition( 'done' );
1528 }
1529
1530 private function emitEventsIfNeeded(): void {
1531 if ( !$this->options['emitEvents'] ) {
1532 return;
1533 }
1534
1535 $this->emitEvents();
1536 }
1537
1541 public function emitEvents(): void {
1542 if ( !( $this->options['allowEvents'] ?? true ) ) {
1543 throw new LogicException( 'dispatchPageUpdatedEvent was disabled on this updater' );
1544 }
1545
1546 // don't dispatch again!
1547 $this->options['emitEvents'] = false;
1548 $this->options['allowEvents'] = false;
1549
1550 $pageLatestRevisionChangedEvent = $this->getPageLatestRevisionChangedEvent();
1551 $pageCreatedEvent = $this->getPageCreatedEvent();
1552
1553 if (
1554 $pageLatestRevisionChangedEvent->getPageRecordBefore() === null &&
1555 !$this->options['created']
1556 ) {
1557 // if the page wasn't just created, we need the state before
1558 throw new LogicException( 'Missing page state before update' );
1559 }
1560
1561 $this->eventDispatcher->dispatch(
1562 $pageLatestRevisionChangedEvent,
1563 $this->loadbalancerFactory
1564 );
1565
1566 if ( $pageCreatedEvent ) {
1567 // NOTE: Emit PageCreated after PageLatestRevisionChanged, because the creation
1568 // is only finished after the revision has been set.
1569 $this->eventDispatcher->dispatch( $pageCreatedEvent, $this->loadbalancerFactory );
1570 }
1571 }
1572
1573 private function getNominalPerformer(): UserIdentity {
1575 $performer = $this->options['triggeringUser'] ?? $this->user;
1576 '@phan-var UserIdentity $performer';
1577
1578 return $performer;
1579 }
1580
1581 private function getPageLatestRevisionChangedEvent(): PageLatestRevisionChangedEvent {
1582 if ( $this->pageLatestRevisionChangedEvent ) {
1583 return $this->pageLatestRevisionChangedEvent;
1584 }
1585
1586 $this->assertHasRevision( __METHOD__ );
1587
1588 $flags = array_intersect_key(
1589 $this->options,
1590 PageLatestRevisionChangedEvent::DEFAULT_FLAGS
1591 );
1592
1593 $pageRecordBefore = $this->pageState['oldRecord'] ?? null;
1594 $pageRecordAfter = $this->getWikiPage()->toPageRecord();
1595
1596 $revisionBefore = $this->getOldRevision();
1597 $revisionAfter = $this->getRevision();
1598
1599 if ( $this->options['created'] ) {
1600 // Page creation. No prior state.
1601 // Force null to make sure we don't get confused during imports when
1602 // updates are triggered after importing the last revision of several.
1603 // In that case, the page and older revisions do already exist when
1604 // the DerivedPageDataUpdater is initialized, because they were
1605 // created during the import. But they didn't exist prior to the
1606 // import (based on the fact that the 'created' flag is set).
1607 $pageRecordBefore = null;
1608 $revisionBefore = null;
1609 } elseif ( !$this->options['changed'] ) {
1610 // Null edit. Should already be the same, just make sure.
1611 $pageRecordBefore = $pageRecordAfter;
1612 }
1613
1614 if ( $revisionBefore && $revisionAfter->getId() === $revisionBefore->getId() ) {
1615 // This is a null edit, flag it as a reconciliation request.
1616 $flags[ PageLatestRevisionChangedEvent::FLAG_RECONCILIATION_REQUEST ] = true;
1617 }
1618
1619 if ( $pageRecordBefore === null && !$this->options['created'] ) {
1620 // If the page wasn't just created, we need the state before.
1621 // If we are not actually emitting the event, we can ignore the issue.
1622 // This is needed to support the deprecated WikiPage::doEditUpdates()
1623 // method. Once that is gone, we can remove this conditional.
1624 if ( $this->options['emitEvents'] ) {
1625 throw new LogicException( 'Missing page state before update' );
1626 }
1627 }
1628
1629 $this->pageLatestRevisionChangedEvent = new PageLatestRevisionChangedEvent(
1630 $this->options['cause'] ?? PageUpdateCauses::CAUSE_EDIT,
1631 $pageRecordBefore,
1632 $pageRecordAfter,
1633 $revisionBefore,
1634 $revisionAfter,
1635 $this->getRevisionSlotsUpdate(),
1636 $this->options['editResult'] ?? null,
1637 $this->getNominalPerformer(),
1638 $this->options['tags'] ?? [],
1639 $flags,
1640 $this->options['rcPatrolStatus'] ?? 0,
1641 );
1642
1643 return $this->pageLatestRevisionChangedEvent;
1644 }
1645
1646 private function getPageCreatedEvent(): ?PageCreatedEvent {
1647 if ( !$this->options['created'] ) {
1648 return null;
1649 }
1650
1651 $pageRecordAfter = $this->getWikiPage()->toPageRecord();
1652
1653 return new PageCreatedEvent(
1654 $this->options['cause'] ?? PageUpdateCauses::CAUSE_EDIT,
1655 $pageRecordAfter,
1656 $this->getRevision(),
1657 $this->getNominalPerformer(),
1658 $this->options['reason'] ?? $this->getRevision()->getComment()->text,
1659 );
1660 }
1661
1662 private function triggerParserCacheUpdate() {
1663 $this->assertHasRevision( __METHOD__ );
1664
1665 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
1666 $userParserOptions = ParserOptions::newFromUser( $this->user );
1667
1668 // Decide whether to save the final canonical parser output based on the fact that
1669 // users are typically redirected to viewing pages right after they edit those pages.
1670 // Due to vary-revision-id, getting/saving that output here might require a reparse.
1671 if ( $userParserOptions->matchesForCacheKey( $this->getCanonicalParserOptions() ) ) {
1672 // Whether getting the final output requires a reparse or not, the user will
1673 // need canonical output anyway, since that is what their parser options use.
1674 // A reparse now at least has the benefit of various warm process caches.
1675 $this->doParserCacheUpdate();
1676 } else {
1677 // If the user does not have canonical parse options, then don't risk another parse
1678 // to make output they cannot use on the page refresh that typically occurs after
1679 // editing. Doing the parser output save post-send will still benefit *other* users.
1680 DeferredUpdates::addCallableUpdate( function () {
1681 $this->doParserCacheUpdate();
1682 } );
1683 }
1684 }
1685
1694 private function maybeAddRecreateChangeTag( WikiPage $wikiPage, int $revisionId ) {
1695 $dbr = $this->loadbalancerFactory->getReplicaDatabase();
1696
1697 if ( $dbr->newSelectQueryBuilder()
1698 ->select( [ '1' ] )
1699 ->from( 'logging' )
1700 ->where( [
1701 'log_type' => 'delete',
1702 'log_title' => $wikiPage->getTitle()->getDBkey(),
1703 'log_namespace' => $wikiPage->getNamespace(),
1704 ] )
1705 ->where(
1706 $dbr->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) .
1707 ' != ' . LogPage::DELETED_ACTION // T385792
1708 )->caller( __METHOD__ )->limit( 1 )->fetchField() ) {
1709 $this->changeTagsStore->addTags(
1710 [ ChangeTags::TAG_RECREATE ],
1711 null,
1712 $revisionId );
1713 }
1714 }
1715
1731 public function doSecondaryDataUpdates( array $options = [] ) {
1732 $this->assertHasRevision( __METHOD__ );
1733 $options += [ 'recursive' => false, 'defer' => false, 'freshness' => false ];
1734 $deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
1735 if ( !in_array( $options['defer'], $deferValues, true ) ) {
1736 throw new InvalidArgumentException( 'Invalid value for defer: ' . $options['defer'] );
1737 }
1738
1739 $triggeringUser = $this->options['triggeringUser'] ?? $this->user;
1740 [ $causeAction, $causeAgent ] = $this->getCauseForTracing();
1741 if ( isset( $options['known-revision-output'] ) ) {
1742 $this->getRenderedRevision()->setRevisionParserOutput( $options['known-revision-output'] );
1743 }
1744
1745 // Bundle all of the data updates into a single deferred update wrapper so that
1746 // any failure will cause at most one refreshLinks job to be enqueued by
1747 // DeferredUpdates::doUpdates(). This is hard to do when there are many separate
1748 // updates that are not defined as being related.
1749 $update = new RefreshSecondaryDataUpdate(
1750 $this->loadbalancerFactory,
1751 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Already checked
1752 $triggeringUser,
1753 $this->wikiPage,
1754 $this->revision,
1755 $this,
1756 [ 'recursive' => $options['recursive'], 'freshness' => $options['freshness'] ]
1757 );
1758 $update->setCause( $causeAction, $causeAgent );
1759
1760 if ( $options['defer'] === false ) {
1761 DeferredUpdates::attemptUpdate( $update );
1762 } else {
1763 DeferredUpdates::addUpdate( $update, $options['defer'] );
1764 }
1765 }
1766
1773 public function doParserCacheUpdate() {
1774 $this->assertHasRevision( __METHOD__ );
1775
1776 $wikiPage = $this->getWikiPage(); // TODO: ParserCache should accept a RevisionRecord instead
1777
1778 // NOTE: this may trigger the first parsing of the new content after an edit (when not
1779 // using pre-generated stashed output).
1780 // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse
1781 // to be performed post-send. The client could already follow a HTTP redirect to the
1782 // page view, but would then have to wait for a response until rendering is complete.
1783 $output = $this->getCanonicalParserOutput();
1784
1785 // Save it to the parser cache. Use the revision timestamp in the case of a
1786 // freshly saved edit, as that matches page_touched and a mismatch would trigger an
1787 // unnecessary reparse.
1788 $timestamp = $this->options['newrev'] ? $this->revision->getTimestamp()
1789 : $output->getCacheTime();
1790 $this->parserCache->save(
1791 $output, $wikiPage, $this->getCanonicalParserOptions(),
1792 $timestamp, $this->revision->getId()
1793 );
1794
1795 // If we enable cache warming with parsoid outputs, let's do it at the same
1796 // time we're populating the parser cache with pre-generated HTML.
1797 // Use OPT_FORCE_PARSE to avoid a useless cache lookup.
1798 if ( $this->serviceOptions->get( MainConfigNames::ParsoidCacheConfig )['WarmParsoidParserCache'] ) {
1799 $cacheWarmingParams = $this->getCauseForTracing();
1800 $cacheWarmingParams['options'] = ParserOutputAccess::OPT_FORCE_PARSE;
1801
1802 $this->jobQueueGroup->lazyPush(
1803 ParsoidCachePrewarmJob::newSpec(
1804 $this->revision->getId(),
1805 $wikiPage->toPageRecord(),
1806 $cacheWarmingParams
1807 )
1808 );
1809 }
1810 }
1811
1812}
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:71
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 ArticleCountMethod
Name constant for the ArticleCountMethod 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(int $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.
getComment(int $audience=self::FOR_PUBLIC, ?Authority $performer=null)
Fetch revision comment, if it's available to the specified audience.
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.
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.
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.
__construct(private readonly ServiceOptions $serviceOptions, 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,)
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:3089
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, 'EnableChunkedUploads'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'DefaultLockManager'=> null, 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> true, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -l $lang -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'MaxAnimatedWebPArea'=> 12500000, 'WebPThumbnailType'=>['webp', 'image/webp',], 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'RestTermsOfServiceUrl'=> null, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'RemoteVirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'SplitParsoidParserCache'=> true, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'EnableSectionShare'=> false, 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'RestrictUserPageEditing' => false, 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', 'managesessions' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => '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' => [ ], 'RestLocalModuleTestBaseUrl' => null, 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], 'UseParsoidLinksUpdate' => null, 'UseParsoidMessages' => null, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'DefaultLockManager' => [ 'string', 'null', ], 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'WebPThumbnailType' => 'array', 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'RestTermsOfServiceUrl' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'RemoteVirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'SplitParsoidParserCache' => 'boolean', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'RestrictUserPageEditing' => 'boolean', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'ReauthenticateForActions' => 'object', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RestrictedTagViewRights' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestLocalModuleTestBaseUrl' => [ 'string', 'null', ], 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', 'UseParsoidLinksUpdate' => [ 'boolean', 'null', ], 'UseParsoidMessages' => [ 'boolean', 'null', ], ], 'mergeStrategy' => [ 'WebPThumbnailType' => 'replace', 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_recursive', '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', ], ], ], ], ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], 'skipRedirects' => [ 'type' => 'bool', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'availability' => [ 'type' => 'string', ], ], 'required' => [ 'availability', ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Content objects represent page content, e.g.
Definition Content.php:28
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.