MediaWiki master
ApiMain.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\Api;
11
12use LogicException;
13use MediaWiki;
36use Profiler;
37use Throwable;
38use UnexpectedValueException;
39use Wikimedia\AtEase\AtEase;
44use Wikimedia\ScopedCallback;
46use Wikimedia\Timestamp\ConvertibleTimestamp;
47use Wikimedia\Timestamp\TimestampException;
48use Wikimedia\Timestamp\TimestampFormat as TS;
49
66class ApiMain extends ApiBase {
70 private const API_DEFAULT_FORMAT = 'jsonfm';
71
75 private const API_DEFAULT_USELANG = 'user';
76
80 private const MODULES = [
81 'login' => [
82 'class' => ApiLogin::class,
83 'services' => [
84 'AuthManager',
85 'UserIdentityUtils'
86 ],
87 ],
88 'clientlogin' => [
89 'class' => ApiClientLogin::class,
90 'services' => [
91 'AuthManager',
92 'UrlUtils',
93 ],
94 ],
95 'logout' => [
96 'class' => ApiLogout::class,
97 ],
98 'createaccount' => [
99 'class' => ApiAMCreateAccount::class,
100 'services' => [
101 'AuthManager',
102 'UrlUtils',
103 ],
104 ],
105 'linkaccount' => [
106 'class' => ApiLinkAccount::class,
107 'services' => [
108 'AuthManager',
109 'UrlUtils',
110 ],
111 ],
112 'unlinkaccount' => [
113 'class' => ApiRemoveAuthenticationData::class,
114 'services' => [
115 'AuthManager',
116 ],
117 ],
118 'changeauthenticationdata' => [
119 'class' => ApiChangeAuthenticationData::class,
120 'services' => [
121 'AuthManager',
122 ],
123 ],
124 'removeauthenticationdata' => [
125 'class' => ApiRemoveAuthenticationData::class,
126 'services' => [
127 'AuthManager',
128 ],
129 ],
130 'resetpassword' => [
131 'class' => ApiResetPassword::class,
132 'services' => [
133 'PasswordReset',
134 ]
135 ],
136 'query' => [
137 'class' => ApiQuery::class,
138 'services' => [
139 'ObjectFactory',
140 'WikiExporterFactory',
141 'TitleFormatter',
142 'TitleFactory',
143 ]
144 ],
145 'expandtemplates' => [
146 'class' => ApiExpandTemplates::class,
147 'services' => [
148 'RevisionStore',
149 'ParserFactory',
150 ]
151 ],
152 'parse' => [
153 'class' => ApiParse::class,
154 'services' => [
155 'RevisionLookup',
156 'SkinFactory',
157 'LanguageNameUtils',
158 'LinkBatchFactory',
159 'LinkCache',
160 'ContentHandlerFactory',
161 'ParserFactory',
162 'WikiPageFactory',
163 'ContentRenderer',
164 'ContentTransformer',
165 'CommentFormatter',
166 'TempUserCreator',
167 'UserFactory',
168 'UrlUtils',
169 'TitleFormatter',
170 'JsonCodec',
171 ]
172 ],
173 'stashedit' => [
174 'class' => ApiStashEdit::class,
175 'services' => [
176 'ContentHandlerFactory',
177 'PageEditStash',
178 'RevisionLookup',
179 'StatsFactory',
180 'WikiPageFactory',
181 'TempUserCreator',
182 'UserFactory',
183 ]
184 ],
185 'opensearch' => [
186 'class' => ApiOpenSearch::class,
187 'services' => [
188 'LinkBatchFactory',
189 'SearchEngineConfig',
190 'SearchEngineFactory',
191 'UrlUtils',
192 ]
193 ],
194 'feedcontributions' => [
195 'class' => ApiFeedContributions::class,
196 'services' => [
197 'RevisionStore',
198 'LinkRenderer',
199 'LinkBatchFactory',
200 'HookContainer',
201 'DBLoadBalancerFactory',
202 'NamespaceInfo',
203 'UserFactory',
204 'CommentFormatter',
205 ]
206 ],
207 'feedrecentchanges' => [
208 'class' => ApiFeedRecentChanges::class,
209 'services' => [
210 'SpecialPageFactory',
211 'TempUserConfig',
212 ]
213 ],
214 'feedwatchlist' => [
215 'class' => ApiFeedWatchlist::class,
216 'services' => [
217 'ParserFactory',
218 ]
219 ],
220 'help' => [
221 'class' => ApiHelp::class,
222 'services' => [
223 'SkinFactory',
224 ]
225 ],
226 'paraminfo' => [
227 'class' => ApiParamInfo::class,
228 'services' => [
229 'UserFactory',
230 ],
231 ],
232 'rsd' => [
233 'class' => ApiRsd::class,
234 ],
235 'compare' => [
236 'class' => ApiComparePages::class,
237 'services' => [
238 'RevisionStore',
239 'ArchivedRevisionLookup',
240 'SlotRoleRegistry',
241 'ContentHandlerFactory',
242 'ContentTransformer',
243 'CommentFormatter',
244 'TempUserCreator',
245 'UserFactory',
246 ]
247 ],
248 'checktoken' => [
249 'class' => ApiCheckToken::class,
250 ],
251 'cspreport' => [
252 'class' => ApiCSPReport::class,
253 'services' => [
254 'UrlUtils',
255 ]
256 ],
257 'validatepassword' => [
258 'class' => ApiValidatePassword::class,
259 'services' => [
260 'AuthManager',
261 'UserFactory',
262 ]
263 ],
264
265 // Write modules
266 'purge' => [
267 'class' => ApiPurge::class,
268 'services' => [
269 'WikiPageFactory',
270 'TitleFormatter',
271 ],
272 ],
273 'setnotificationtimestamp' => [
274 'class' => ApiSetNotificationTimestamp::class,
275 'services' => [
276 'DBLoadBalancerFactory',
277 'RevisionStore',
278 'WatchedItemStore',
279 'TitleFormatter',
280 'TitleFactory',
281 ]
282 ],
283 'rollback' => [
284 'class' => ApiRollback::class,
285 'services' => [
286 'RollbackPageFactory',
287 'WatchlistManager',
288 'WatchedItemStore',
289 'UserOptionsLookup',
290 ]
291 ],
292 'delete' => [
293 'class' => ApiDelete::class,
294 'services' => [
295 'RepoGroup',
296 'WatchlistManager',
297 'WatchedItemStore',
298 'UserOptionsLookup',
299 'DeletePageFactory',
300 ]
301 ],
302 'undelete' => [
303 'class' => ApiUndelete::class,
304 'services' => [
305 'WatchlistManager',
306 'WatchedItemStore',
307 'UserOptionsLookup',
308 'UndeletePageFactory',
309 'WikiPageFactory',
310 ]
311 ],
312 'protect' => [
313 'class' => ApiProtect::class,
314 'services' => [
315 'WatchlistManager',
316 'WatchedItemStore',
317 'UserOptionsLookup',
318 'RestrictionStore',
319 ]
320 ],
321 'block' => [
322 'class' => ApiBlock::class,
323 'services' => [
324 'BlockPermissionCheckerFactory',
325 'BlockUserFactory',
326 'TitleFactory',
327 'UserIdentityLookup',
328 'WatchedItemStore',
329 'BlockTargetFactory',
330 'BlockActionInfo',
331 'DatabaseBlockStore',
332 'WatchlistManager',
333 'UserOptionsLookup',
334 ]
335 ],
336 'unblock' => [
337 'class' => ApiUnblock::class,
338 'services' => [
339 'BlockPermissionCheckerFactory',
340 'UnblockUserFactory',
341 'UserIdentityLookup',
342 'WatchedItemStore',
343 'WatchlistManager',
344 'UserOptionsLookup',
345 'DatabaseBlockStore',
346 'BlockTargetFactory',
347 ]
348 ],
349 'move' => [
350 'class' => ApiMove::class,
351 'services' => [
352 'MovePageFactory',
353 'RepoGroup',
354 'WatchlistManager',
355 'WatchedItemStore',
356 'UserOptionsLookup',
357 ]
358 ],
359 'edit' => [
360 'class' => ApiEditPage::class,
361 'services' => [
362 'ContentHandlerFactory',
363 'RevisionLookup',
364 'WatchedItemStore',
365 'WikiPageFactory',
366 'WatchlistManager',
367 'UserOptionsLookup',
368 'RedirectLookup',
369 'TempUserCreator',
370 'UserFactory',
371 ]
372 ],
373 'upload' => [
374 'class' => ApiUpload::class,
375 'services' => [
376 'JobQueueGroup',
377 'WatchlistManager',
378 'WatchedItemStore',
379 'UserOptionsLookup',
380 ]
381 ],
382 'filerevert' => [
383 'class' => ApiFileRevert::class,
384 'services' => [
385 'RepoGroup',
386 ]
387 ],
388 'emailuser' => [
389 'class' => ApiEmailUser::class,
390 'services' => [
391 'EmailUserFactory',
392 'UserFactory',
393 ]
394 ],
395 'watch' => [
396 'class' => ApiWatch::class,
397 'services' => [
398 'WatchlistManager',
399 'TitleFormatter',
400 ]
401 ],
402 'patrol' => [
403 'class' => ApiPatrol::class,
404 'services' => [
405 'RevisionStore',
406 'PatrolManager',
407 'RecentChangeLookup',
408 ]
409 ],
410 'import' => [
411 'class' => ApiImport::class,
412 'services' => [
413 'WikiImporterFactory',
414 ]
415 ],
416 'clearhasmsg' => [
417 'class' => ApiClearHasMsg::class,
418 'services' => [
419 'TalkPageNotificationManager',
420 ]
421 ],
422 'userrights' => [
423 'class' => ApiUserrights::class,
424 'services' => [
425 'UserGroupManager',
426 'WatchedItemStore',
427 'WatchlistManager',
428 'UserOptionsLookup',
429 'UserGroupAssignmentService',
430 'MultiFormatUserIdentityLookup',
431 ]
432 ],
433 'options' => [
434 'class' => ApiOptions::class,
435 'services' => [
436 'UserOptionsManager',
437 'PreferencesFactory',
438 ],
439 ],
440 'imagerotate' => [
441 'class' => ApiImageRotate::class,
442 'services' => [
443 'RepoGroup',
444 'TempFSFileFactory',
445 'TitleFactory',
446 ]
447 ],
448 'revisiondelete' => [
449 'class' => ApiRevisionDelete::class,
450 ],
451 'managetags' => [
452 'class' => ApiManageTags::class,
453 ],
454 'tag' => [
455 'class' => ApiTag::class,
456 'services' => [
457 'DBLoadBalancerFactory',
458 'RevisionStore',
459 'ChangeTagsStore',
460 'RecentChangeLookup',
461 ]
462 ],
463 'mergehistory' => [
464 'class' => ApiMergeHistory::class,
465 'services' => [
466 'MergeHistoryFactory',
467 ],
468 ],
469 'setpagelanguage' => [
470 'class' => ApiSetPageLanguage::class,
471 'services' => [
472 'DBLoadBalancerFactory',
473 'LanguageNameUtils',
474 ]
475 ],
476 'changecontentmodel' => [
477 'class' => ApiChangeContentModel::class,
478 'services' => [
479 'ContentHandlerFactory',
480 'ContentModelChangeFactory',
481 ]
482 ],
483 'acquiretempusername' => [
484 'class' => ApiAcquireTempUserName::class,
485 'services' => [
486 'TempUserCreator',
487 ]
488 ],
489 'languagesearch' => [
490 'class' => ApiLanguageSearch::class,
491 'services' => [
492 'LanguageNameSearch',
493 ],
494 ],
495 ];
496
500 private const FORMATS = [
501 'json' => [
502 'class' => ApiFormatJson::class,
503 ],
504 'jsonfm' => [
505 'class' => ApiFormatJson::class,
506 ],
507 'php' => [
508 'class' => ApiFormatPhp::class,
509 ],
510 'phpfm' => [
511 'class' => ApiFormatPhp::class,
512 ],
513 'xml' => [
514 'class' => ApiFormatXml::class,
515 ],
516 'xmlfm' => [
517 'class' => ApiFormatXml::class,
518 ],
519 'rawfm' => [
520 'class' => ApiFormatJson::class,
521 ],
522 'none' => [
523 'class' => ApiFormatNone::class,
524 ],
525 ];
526
533 private const RIGHTS_MAP = [
534 'apihighlimits' => [
535 'msg' => 'api-help-right-apihighlimits',
537 ]
538 ];
539
541 private $mPrinter;
542
544 private $mModuleMgr;
545
547 private $mResult;
548
550 private $mErrorFormatter;
551
553 private $mParamValidator;
554
556 private $mContinuationManager;
557
559 private $mAction;
560
562 private $mEnableWrite;
563
565 private $mInternalMode;
566
568 private $mModule;
569
571 private $mCacheMode = 'private';
572
574 private $mCacheControl = [];
575
577 private $mParamsUsed = [];
578
580 private $mParamsSensitive = [];
581
583 private $lacksSameOriginSecurity = null;
584
586 private $statsFactory;
587
599 public function __construct( $context = null, $enableWrite = false, $internal = null ) {
600 if ( $context === null ) {
601 $context = RequestContext::getMain();
602 } elseif ( $context instanceof WebRequest ) {
603 // BC for pre-1.19
604 $request = $context;
605 $context = RequestContext::getMain();
606 }
607 // We set a derivative context so we can change stuff later
608 $derivativeContext = new DerivativeContext( $context );
609 $this->setContext( $derivativeContext );
610
611 if ( isset( $request ) ) {
612 $derivativeContext->setRequest( $request );
613 } else {
614 $request = $this->getRequest();
615 }
616
617 $this->mInternalMode = $internal ?? ( $request instanceof FauxRequest );
618
619 // Special handling for the main module: $parent === $this
620 parent::__construct( $this, $this->mInternalMode ? 'main_int' : 'main' );
621
622 $config = $this->getConfig();
623 // TODO inject stuff, see T265644
624 $services = MediaWikiServices::getInstance();
625
626 if ( !$this->mInternalMode ) {
627 // If we're in a mode that breaks the same-origin policy, strip
628 // user credentials for security.
629 if ( $this->lacksSameOriginSecurity() ) {
630 wfDebug( "API: stripping user credentials when the same-origin policy is not applied" );
631 $user = $services->getUserFactory()->newAnonymous();
633 $derivativeContext->setUser( $user );
634 $request->response()->header( 'MediaWiki-Login-Suppressed: true' );
635 }
636 }
637
638 $this->mParamValidator = new ApiParamValidator(
639 $this,
640 $services->getObjectFactory()
641 );
642
643 $this->statsFactory = $services->getStatsFactory();
644
645 $this->mResult =
647
648 // Setup uselang. This doesn't use $this->getParameter()
649 // because we're not ready to handle errors yet.
650 // Optimisation: Avoid slow getVal(), this isn't user-generated content.
651 $uselang = $request->getRawVal( 'uselang' ) ?? self::API_DEFAULT_USELANG;
652 if ( $uselang === 'user' ) {
653 // Assume the parent context is going to return the user language
654 // for uselang=user (see T85635).
655 } else {
656 if ( $uselang === 'content' ) {
657 $uselang = $services->getContentLanguageCode()->toString();
658 }
659 $code = RequestContext::sanitizeLangCode( $uselang );
660 $derivativeContext->setLanguage( $code );
661 if ( !$this->mInternalMode ) {
662 // phpcs:disable MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage
663 global $wgLang;
664 $wgLang = $derivativeContext->getLanguage();
665 RequestContext::getMain()->setLanguage( $wgLang );
666 // phpcs:enable
667 }
668 }
669
670 // Set up the error formatter. This doesn't use $this->getParameter()
671 // because we're not ready to handle errors yet.
672 // Optimisation: Avoid slow getVal(), this isn't user-generated content.
673 $errorFormat = $request->getRawVal( 'errorformat' ) ?? 'bc';
674 $errorLangCode = $request->getRawVal( 'errorlang' ) ?? 'uselang';
675 $errorsUseDB = $request->getCheck( 'errorsuselocal' );
676 if ( in_array( $errorFormat, [ 'plaintext', 'wikitext', 'html', 'raw', 'none' ], true ) ) {
677 if ( $errorLangCode === 'uselang' ) {
678 $errorLang = $this->getLanguage();
679 } elseif ( $errorLangCode === 'content' ) {
680 $errorLang = $services->getContentLanguage();
681 } else {
682 $errorLangCode = RequestContext::sanitizeLangCode( $errorLangCode );
683 $errorLang = $services->getLanguageFactory()->getLanguage( $errorLangCode );
684 }
685 $this->mErrorFormatter = new ApiErrorFormatter(
686 $this->mResult,
687 $errorLang,
688 $errorFormat,
689 $errorsUseDB
690 );
691 } else {
692 $this->mErrorFormatter = new ApiErrorFormatter_BackCompat( $this->mResult );
693 }
694 $this->mResult->setErrorFormatter( $this->getErrorFormatter() );
695
696 $this->mModuleMgr = new ApiModuleManager(
697 $this,
698 $services->getObjectFactory()
699 );
700 $this->mModuleMgr->addModules( self::MODULES, 'action' );
701 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIModules ), 'action' );
702 $this->mModuleMgr->addModules( self::FORMATS, 'format' );
703 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIFormatModules ), 'format' );
704
705 $this->getHookRunner()->onApiMain__moduleManager( $this->mModuleMgr );
706
707 $this->mContinuationManager = null;
708 $this->mEnableWrite = $enableWrite;
709 }
710
715 public function isInternalMode() {
716 return $this->mInternalMode;
717 }
718
724 public function getResult() {
725 return $this->mResult;
726 }
727
732 public function lacksSameOriginSecurity() {
733 if ( $this->lacksSameOriginSecurity !== null ) {
734 return $this->lacksSameOriginSecurity;
735 }
736
737 $request = $this->getRequest();
738
739 // JSONP mode
740 if ( $request->getCheck( 'callback' ) ||
741 // Anonymous CORS
742 $request->getRawVal( 'origin' ) === '*' ||
743 // Header to be used from XMLHTTPRequest when the request might
744 // otherwise be used for XSS.
745 $request->getHeader( 'Treat-as-Untrusted' ) !== false ||
746 (
747 // Authenticated CORS with unsupported session provider (including preflight request)
748 $request->getCheck( 'crossorigin' ) &&
749 !$request->getSession()->getProvider()->safeAgainstCsrf()
750 )
751 ) {
752 $this->lacksSameOriginSecurity = true;
753 return true;
754 }
755
756 // Allow extensions to override.
757 $this->lacksSameOriginSecurity = !$this->getHookRunner()
758 ->onRequestHasSameOriginSecurity( $request );
759 return $this->lacksSameOriginSecurity;
760 }
761
766 public function getErrorFormatter() {
767 return $this->mErrorFormatter;
768 }
769
773 public function getContinuationManager() {
774 return $this->mContinuationManager;
775 }
776
780 public function setContinuationManager( ?ApiContinuationManager $manager = null ) {
781 if ( $manager !== null && $this->mContinuationManager !== null ) {
782 throw new UnexpectedValueException(
783 __METHOD__ . ': tried to set manager from ' . $manager->getSource() .
784 ' when a manager is already set from ' . $this->mContinuationManager->getSource()
785 );
786 }
787 $this->mContinuationManager = $manager;
788 }
789
791 return $this->mParamValidator;
792 }
793
799 public function getModule() {
800 return $this->mModule;
801 }
802
808 public function getStatsFactory() {
809 return $this->getMain()->statsFactory;
810 }
811
817 public function getPrinter() {
818 return $this->mPrinter;
819 }
820
826 public function setCacheMaxAge( $maxage ) {
827 $this->setCacheControl( [
828 'max-age' => $maxage,
829 's-maxage' => $maxage
830 ] );
831 }
832
858 public function setCacheMode( $mode ) {
859 if ( !in_array( $mode, [ 'private', 'public', 'anon-public-user-private' ] ) ) {
860 wfDebug( __METHOD__ . ": unrecognised cache mode \"$mode\"" );
861
862 // Ignore for forwards-compatibility
863 return;
864 }
865
866 if ( !$this->getPermissionManager()->isEveryoneAllowed( 'read' ) ) {
867 // Private wiki, only private headers
868 if ( $mode !== 'private' ) {
869 wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki" );
870
871 return;
872 }
873 }
874
875 if ( $mode === 'public' && $this->getParameter( 'uselang' ) === 'user' ) {
876 // User language is used for i18n, so we don't want to publicly
877 // cache. Anons are ok, because if they have non-default language
878 // then there's an appropriate Vary header set by whatever set
879 // their non-default language.
880 wfDebug( __METHOD__ . ": downgrading cache mode 'public' to " .
881 "'anon-public-user-private' due to uselang=user" );
882 $mode = 'anon-public-user-private';
883 }
884
885 wfDebug( __METHOD__ . ": setting cache mode $mode" );
886 $this->mCacheMode = $mode;
887 }
888
890 public function getCacheMode() {
891 return $this->mCacheMode;
892 }
893
904 public function setCacheControl( $directives ) {
905 $this->mCacheControl = $directives + $this->mCacheControl;
906 }
907
915 public function createPrinterByName( $format ) {
916 $printer = $this->mModuleMgr->getModule( $format, 'format', /* $ignoreCache */ true );
917 if ( $printer === null ) {
918 $this->dieWithError(
919 [ 'apierror-unknownformat', wfEscapeWikiText( $format ) ], 'unknown_format'
920 );
921 }
922
923 // @phan-suppress-next-line PhanTypeMismatchReturnSuperType
924 return $printer;
925 }
926
930 public function execute() {
931 if ( $this->mInternalMode ) {
932 $this->executeAction();
933 } else {
934 $this->executeActionWithErrorHandling();
935 }
936 }
937
942 protected function executeActionWithErrorHandling() {
943 // Verify the CORS header before executing the action
944 if ( !$this->handleCORS() ) {
945 // handleCORS() has sent a 403, abort
946 return;
947 }
948
949 // Exit here if the request method was OPTIONS
950 // (assume there will be a followup GET or POST)
951 if ( $this->getRequest()->getMethod() === 'OPTIONS' ) {
952 return;
953 }
954
955 // In case an error occurs during data output,
956 // clear the output buffer and print just the error information
957 $obLevel = ob_get_level();
958 ob_start();
959
960 $t = microtime( true );
961 $isError = false;
962 try {
963 $this->executeAction();
964 $runTime = microtime( true ) - $t;
965 $this->logRequest( $runTime );
966
967 $this->statsFactory->getTiming( 'api_executeTiming_seconds' )
968 ->setLabel( 'module', $this->mModule->getModuleName() )
969 ->observe( 1000 * $runTime );
970
971 if ( !$this->mModule || $this->mModule->getModuleName() !== 'query' ) {
972 // Skip query module metrics; we will record them in the query module itself.
973 $this->recordUnifiedMetrics( $runTime );
974 }
975 } catch ( Throwable $e ) {
976 // If executeAction threw before the time was set, reset it
977 $runTime ??= microtime( true ) - $t;
978 $this->handleException( $e, $runTime );
979 $this->logRequest( microtime( true ) - $t, $e );
980 $isError = true;
981 }
982
983 // Disable the client cache on the output so that BlockManager::trackBlockWithCookie is executed
984 // as part of MediaWiki::preOutputCommit().
985 if (
986 $this->mCacheMode === 'private'
987 || (
988 $this->mCacheMode === 'anon-public-user-private'
989 && $this->getRequest()->getSession()->isPersistent()
990 )
991 ) {
992 $this->getContext()->getOutput()->disableClientCache();
993 $this->getContext()->getOutput()->considerCacheSettingsFinal();
994 }
995
996 // Commit DBs and send any related cookies and headers
997 MediaWiki::preOutputCommit( $this->getContext() );
998
999 // Send cache headers after any code which might generate an error, to
1000 // avoid sending public cache headers for errors.
1001 $this->sendCacheHeaders( $isError );
1002
1003 // Executing the action might have already messed with the output
1004 // buffers.
1005 while ( ob_get_level() > $obLevel ) {
1006 ob_end_flush();
1007 }
1008 }
1009
1017 protected function handleException( Throwable $e, $latency = 0 ) {
1018 $statsModuleName = $this->mModule ? $this->mModule->getModuleName() : 'main';
1019
1020 // Collect stats on errors (T396613).
1021 // NOTE: We only count fatal errors, a mere call to addError() or
1022 // addWarning() does not count towards these states. That could
1023 // be added in the future, but should use a different stats key.
1024 $stats = $this->statsFactory->getCounter( 'api_errors' )
1025 ->setLabel( 'module', $statsModuleName );
1026
1027 // T65145: Rollback any open database transactions
1028 if ( !$e instanceof ApiUsageException ) {
1029 // ApiUsageExceptions are intentional, so don't rollback if that's the case
1030 MWExceptionHandler::rollbackPrimaryChangesAndLog(
1031 $e,
1032 MWExceptionHandler::CAUGHT_BY_ENTRYPOINT
1033 );
1034 $stats->setLabel( 'exception_cause', 'server-error' );
1035 } else {
1036 $stats->setLabel( 'exception_cause', 'client-error' );
1037 }
1038
1039 // Allow extra cleanup and logging
1040 $this->getHookRunner()->onApiMain__onException( $this, $e );
1041
1042 // Handle any kind of exception by outputting properly formatted error message.
1043 // If this fails, an unhandled exception should be thrown so that global error
1044 // handler will process and log it.
1045
1046 $errCodes = $this->substituteResultWithError( $e );
1047 sort( $errCodes );
1048
1049 // Error results should not be cached
1050 $this->setCacheMode( 'private' );
1051
1052 $response = $this->getRequest()->response();
1053 $headerStr = 'MediaWiki-API-Error: ' . implode( ', ', $errCodes );
1054 $response->header( $headerStr );
1055
1056 // Reset and print just the error message
1057 ob_clean();
1058
1059 // Printer may not be initialized if the extractRequestParams() fails for the main module
1060 $this->createErrorPrinter();
1061
1062 $stats->setLabel( 'error_code', implode( '_', $errCodes ) );
1063 $stats->increment();
1064
1065 // Unified metrics
1066 if ( !$this->mModule || $this->mModule->getModuleName() !== 'query' ) {
1067 // Skip query module metrics; we will record them in the query module itself.
1068 $this->recordUnifiedMetrics(
1069 $latency,
1070 [
1071 'status' => implode( '_', $errCodes ), // Failure codes
1072 ]
1073 );
1074
1075 }
1076
1077 // Get desired HTTP code from an ApiUsageException. Don't use codes from other
1078 // exception types, as they are unlikely to be intended as an HTTP code.
1079 $httpCode = $e instanceof ApiUsageException ? $e->getCode() : 0;
1080
1081 $failed = false;
1082 try {
1083 $this->printResult( $httpCode );
1084 } catch ( ApiUsageException $ex ) {
1085 // The error printer itself is failing. Try suppressing its request
1086 // parameters and redo.
1087 $failed = true;
1088 $this->addWarning( 'apiwarn-errorprinterfailed' );
1089 foreach ( $ex->getStatusValue()->getMessages() as $error ) {
1090 try {
1091 $this->mPrinter->addWarning( $error );
1092 } catch ( Throwable ) {
1093 // WTF?
1094 $this->addWarning( $error );
1095 }
1096 }
1097 }
1098 if ( $failed ) {
1099 $this->mPrinter = null;
1100 $this->createErrorPrinter();
1101 // @phan-suppress-next-line PhanNonClassMethodCall False positive
1102 $this->mPrinter->forceDefaultParams();
1103 if ( $httpCode ) {
1104 $response->statusHeader( 200 ); // Reset in case the fallback doesn't want a non-200
1105 }
1106 $this->printResult( $httpCode );
1107 }
1108 }
1109
1120 public static function handleApiBeforeMainException( Throwable $e ) {
1121 ob_start();
1122
1123 try {
1124 $main = new self( RequestContext::getMain(), false );
1125 $main->handleException( $e );
1126 $main->logRequest( 0, $e );
1127 } catch ( Throwable ) {
1128 // Nope, even that didn't work. Punt.
1129 throw $e;
1130 }
1131
1132 // Reset cache headers
1133 $main->sendCacheHeaders( true );
1134
1135 ob_end_flush();
1136 }
1137
1158 protected function handleCORS() {
1159 $originParam = $this->getParameter( 'origin' ); // defaults to null
1160 $crossOriginParam = $this->getParameter( 'crossorigin' ); // defaults to false
1161 if ( $originParam === null && !$crossOriginParam ) {
1162 // No origin/crossorigin parameter, nothing to do
1163 return true;
1164 }
1165
1166 $request = $this->getRequest();
1167 $response = $request->response();
1168 $requestedMethod = $request->getHeader( 'Access-Control-Request-Method' );
1169 $preflight = $request->getMethod() === 'OPTIONS' && $requestedMethod !== false;
1170
1171 $allowTiming = false;
1172 $varyOrigin = true;
1173
1174 if ( $originParam !== null && $crossOriginParam ) {
1175 $response->statusHeader( 403 );
1176 $response->header( 'Cache-control: no-cache' );
1177 echo "'origin' and 'crossorigin' parameters cannot be used together\n";
1178
1179 return false;
1180 }
1181 if ( $crossOriginParam && !$request->getSession()->getProvider()->safeAgainstCsrf() && !$preflight ) {
1182 $response->statusHeader( 403 );
1183 $response->header( 'Cache-control: no-cache' );
1184 $language = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' );
1185 $described = $request->getSession()->getProvider()->describe( $language );
1186 echo "'crossorigin' cannot be used with $described\n";
1187
1188 return false;
1189 }
1190
1191 if ( $originParam === '*' || $crossOriginParam ) {
1192 // Request for CORS without browser-supplied credentials (e.g. cookies):
1193 // may be anonymous (origin=*) or authenticated with request-supplied
1194 // credentials (crossorigin=1 + Authorization header).
1195 // Technically we should check for the presence of an Origin header
1196 // and not process it as CORS if it's not set, but that would
1197 // require us to vary on Origin for all 'origin=*' requests which
1198 // we don't want to do.
1199 $matchedOrigin = true;
1200 $allowOrigin = '*';
1201 $allowCredentials = 'false';
1202 $varyOrigin = false; // No need to vary
1203 } else {
1204 // Non-anonymous CORS, check we allow the domain
1205
1206 // Origin: header is a space-separated list of origins, check all of them
1207 $originHeader = $request->getHeader( 'Origin' );
1208 if ( $originHeader === false ) {
1209 $origins = [];
1210 } else {
1211 $originHeader = trim( $originHeader );
1212 $origins = preg_split( '/\s+/', $originHeader );
1213 }
1214
1215 if ( !in_array( $originParam, $origins ) ) {
1216 // origin parameter set but incorrect
1217 // Send a 403 response
1218 $response->statusHeader( 403 );
1219 $response->header( 'Cache-Control: no-cache' );
1220 echo "'origin' parameter does not match Origin header\n";
1221
1222 return false;
1223 }
1224
1225 $config = $this->getConfig();
1226 $origin = Origin::parseHeaderList( $origins );
1227 $matchedOrigin = $origin->match(
1230 );
1231
1232 $allowOrigin = $originHeader;
1233 $allowCredentials = 'true';
1234 $allowTiming = $originHeader;
1235 }
1236
1237 if ( $matchedOrigin ) {
1238 if ( $preflight ) {
1239 // We allow the actual request to send the following headers
1240 $requestedHeaders = $request->getHeader( 'Access-Control-Request-Headers' );
1241 $allowedHeaders = $this->getConfig()->get( MainConfigNames::AllowedCorsHeaders );
1242 if ( $requestedHeaders !== false ) {
1243 if ( !self::matchRequestedHeaders( $requestedHeaders, $allowedHeaders ) ) {
1244 $response->header( 'MediaWiki-CORS-Rejection: Unsupported header requested in preflight' );
1245 return true;
1246 }
1247 $response->header( 'Access-Control-Allow-Headers: ' . $requestedHeaders );
1248 }
1249
1250 // We only allow the actual request to be GET, POST, or HEAD
1251 $response->header( 'Access-Control-Allow-Methods: POST, GET, HEAD' );
1252 }
1253
1254 $response->header( "Access-Control-Allow-Origin: $allowOrigin" );
1255 $response->header( "Access-Control-Allow-Credentials: $allowCredentials" );
1256 // https://www.w3.org/TR/resource-timing/#timing-allow-origin
1257 if ( $allowTiming !== false ) {
1258 $response->header( "Timing-Allow-Origin: $allowTiming" );
1259 }
1260
1261 if ( !$preflight ) {
1262 $response->header(
1263 'Access-Control-Expose-Headers: MediaWiki-API-Error, Retry-After, X-Database-Lag, '
1264 . 'MediaWiki-Login-Suppressed'
1265 );
1266 }
1267 } else {
1268 $response->header( 'MediaWiki-CORS-Rejection: Origin mismatch' );
1269 }
1270
1271 if ( $varyOrigin ) {
1272 $this->getOutput()->addVaryHeader( 'Origin' );
1273 }
1274
1275 return true;
1276 }
1277
1286 protected static function matchRequestedHeaders( $requestedHeaders, $allowedHeaders ) {
1287 if ( trim( $requestedHeaders ) === '' ) {
1288 return true;
1289 }
1290 $requestedHeaders = explode( ',', $requestedHeaders );
1291 $allowedHeaders = array_change_key_case(
1292 array_fill_keys( $allowedHeaders, true ), CASE_LOWER );
1293 foreach ( $requestedHeaders as $rHeader ) {
1294 $rHeader = strtolower( trim( $rHeader ) );
1295 if ( !isset( $allowedHeaders[$rHeader] ) ) {
1296 LoggerFactory::getInstance( 'api-warning' )->warning(
1297 'CORS preflight failed on requested header: {header}', [
1298 'header' => $rHeader
1299 ]
1300 );
1301 return false;
1302 }
1303 }
1304 return true;
1305 }
1306
1312 protected function sendCacheHeaders( $isError ) {
1313 $response = $this->getRequest()->response();
1314 $out = $this->getOutput();
1315
1316 $out->addVaryHeader( 'Treat-as-Untrusted' );
1317
1318 $config = $this->getConfig();
1319
1320 if ( $config->get( MainConfigNames::VaryOnXFP ) ) {
1321 $out->addVaryHeader( 'X-Forwarded-Proto' );
1322 }
1323
1324 if ( !$isError && $this->mModule &&
1325 ( $this->getRequest()->getMethod() === 'GET' || $this->getRequest()->getMethod() === 'HEAD' )
1326 ) {
1327 $etag = $this->mModule->getConditionalRequestData( 'etag' );
1328 if ( $etag !== null ) {
1329 $response->header( "ETag: $etag" );
1330 }
1331 $lastMod = $this->mModule->getConditionalRequestData( 'last-modified' );
1332 if ( $lastMod !== null ) {
1333 $response->header( 'Last-Modified: ' . wfTimestamp( TS::RFC2822, $lastMod ) );
1334 }
1335 }
1336
1337 // The logic should be:
1338 // $this->mCacheControl['max-age'] is set?
1339 // Use it, the module knows better than our guess.
1340 // !$this->mModule || $this->mModule->isWriteMode(), and mCacheMode is private?
1341 // Use 0 because we can guess caching is probably the wrong thing to do.
1342 // Use $this->getParameter( 'maxage' ), which already defaults to 0.
1343 $maxage = 0;
1344 if ( isset( $this->mCacheControl['max-age'] ) ) {
1345 $maxage = $this->mCacheControl['max-age'];
1346 } elseif ( ( !$isError && $this->mModule && !$this->mModule->isWriteMode() ) ||
1347 $this->mCacheMode !== 'private'
1348 ) {
1349 $maxage = $this->getParameter( 'maxage' );
1350 }
1351 $privateCache = 'private, must-revalidate, max-age=' . $maxage;
1352
1353 if ( $this->mCacheMode == 'private' ) {
1354 $response->header( "Cache-Control: $privateCache" );
1355 return;
1356 }
1357
1358 if ( $this->mCacheMode == 'anon-public-user-private' ) {
1359 $out->addVaryHeader( 'Cookie' );
1360 $response->header( $out->getVaryHeader() );
1361 if ( $this->getRequest()->getSession()->isPersistent() ) {
1362 // Logged in or otherwise has session (e.g. anonymous users who have edited)
1363 // Mark request private
1364 $response->header( "Cache-Control: $privateCache" );
1365
1366 return;
1367 } // else anonymous, send public headers below
1368 }
1369
1370 // Send public headers
1371 $response->header( $out->getVaryHeader() );
1372
1373 // If nobody called setCacheMaxAge(), use the (s)maxage parameters
1374 if ( !isset( $this->mCacheControl['s-maxage'] ) ) {
1375 $this->mCacheControl['s-maxage'] = $this->getParameter( 'smaxage' );
1376 }
1377 if ( !isset( $this->mCacheControl['max-age'] ) ) {
1378 $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' );
1379 }
1380
1381 if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) {
1382 // Public cache not requested
1383 // Sending a Vary header in this case is harmless, and protects us
1384 // against conditional calls of setCacheMaxAge().
1385 $response->header( "Cache-Control: $privateCache" );
1386
1387 return;
1388 }
1389
1390 $this->mCacheControl['public'] = true;
1391
1392 // Send an Expires header
1393 $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] );
1394 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge );
1395 $response->header( 'Expires: ' . wfTimestamp( TS::RFC2822, $expiryUnixTime ) );
1396
1397 // Construct the Cache-Control header
1398 $ccHeader = '';
1399 $separator = '';
1400 foreach ( $this->mCacheControl as $name => $value ) {
1401 if ( is_bool( $value ) ) {
1402 if ( $value ) {
1403 $ccHeader .= $separator . $name;
1404 $separator = ', ';
1405 }
1406 } else {
1407 $ccHeader .= $separator . "$name=$value";
1408 $separator = ', ';
1409 }
1410 }
1411
1412 $response->header( "Cache-Control: $ccHeader" );
1413 }
1414
1418 private function createErrorPrinter() {
1419 if ( !$this->mPrinter ) {
1420 $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT );
1421 if ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) {
1422 $value = self::API_DEFAULT_FORMAT;
1423 }
1424 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable getVal does not return null here
1425 $this->mPrinter = $this->createPrinterByName( $value );
1426 }
1427
1428 // Printer may not be able to handle errors. This is particularly
1429 // likely if the module returns something for getCustomPrinter().
1430 if ( !$this->mPrinter->canPrintErrors() ) {
1431 $this->mPrinter = $this->createPrinterByName( self::API_DEFAULT_FORMAT );
1432 }
1433 }
1434
1450 protected function errorMessagesFromException( Throwable $e, $type = 'error' ) {
1451 $messages = [];
1452 if ( $e instanceof ApiUsageException ) {
1453 foreach ( $e->getStatusValue()->getMessages( $type ) as $msg ) {
1454 $messages[] = ApiMessage::create( $msg );
1455 }
1456 } elseif ( $type !== 'error' ) {
1457 // None of the rest have any messages for non-error types
1458 } else {
1459 // TODO: Avoid embedding arbitrary class names in the error code.
1460 $class = preg_replace( '#^Wikimedia\\\\Rdbms\\\\#', '', get_class( $e ) );
1461 $code = 'internal_api_error_' . $class;
1462 $data = [ 'errorclass' => get_class( $e ) ];
1463 if ( MWExceptionRenderer::shouldShowExceptionDetails() ) {
1464 if ( $e instanceof ILocalizedException ) {
1465 $msg = $e->getMessageObject();
1466 } elseif ( $e instanceof MessageSpecifier ) {
1467 $msg = Message::newFromSpecifier( $e );
1468 } else {
1469 $msg = wfEscapeWikiText( $e->getMessage() );
1470 }
1471 $params = [ 'apierror-exceptioncaught', WebRequest::getRequestId(), $msg ];
1472 } else {
1473 $params = [ 'apierror-exceptioncaughttype', WebRequest::getRequestId(), get_class( $e ) ];
1474 }
1475
1476 $messages[] = ApiMessage::create( $params, $code, $data );
1477 }
1478 return $messages;
1479 }
1480
1486 protected function substituteResultWithError( Throwable $e ) {
1487 $result = $this->getResult();
1488 $formatter = $this->getErrorFormatter();
1489 $config = $this->getConfig();
1490 $errorCodes = [];
1491
1492 // Remember existing warnings and errors across the reset
1493 $errors = $result->getResultData( [ 'errors' ] );
1494 $warnings = $result->getResultData( [ 'warnings' ] );
1495 $result->reset();
1496 if ( $warnings !== null ) {
1497 $result->addValue( null, 'warnings', $warnings, ApiResult::NO_SIZE_CHECK );
1498 }
1499 if ( $errors !== null ) {
1500 $result->addValue( null, 'errors', $errors, ApiResult::NO_SIZE_CHECK );
1501
1502 // Collect the copied error codes for the return value
1503 foreach ( $errors as $error ) {
1504 if ( isset( $error['code'] ) ) {
1505 $errorCodes[$error['code']] = true;
1506 }
1507 }
1508 }
1509
1510 // Add errors from the exception
1511 $modulePath = $e instanceof ApiUsageException ? $e->getModulePath() : null;
1512 foreach ( $this->errorMessagesFromException( $e, 'error' ) as $msg ) {
1513 if ( ApiErrorFormatter::isValidApiCode( $msg->getApiCode() ) ) {
1514 $errorCodes[$msg->getApiCode()] = true;
1515 } else {
1516 LoggerFactory::getInstance( 'api-warning' )->error( 'Invalid API error code "{code}"', [
1517 'code' => $msg->getApiCode(),
1518 'exception' => $e,
1519 ] );
1520 $errorCodes['<invalid-code>'] = true;
1521 }
1522 $formatter->addError( $modulePath, $msg );
1523 }
1524 foreach ( $this->errorMessagesFromException( $e, 'warning' ) as $msg ) {
1525 $formatter->addWarning( $modulePath, $msg );
1526 }
1527
1528 // Add additional data. Path depends on whether we're in BC mode or not.
1529 // Data depends on the type of exception.
1530 if ( $formatter instanceof ApiErrorFormatter_BackCompat ) {
1531 $path = [ 'error' ];
1532 } else {
1533 $path = null;
1534 }
1535 if ( $e instanceof ApiUsageException ) {
1536 $link = (string)MediaWikiServices::getInstance()->getUrlUtils()->expand( wfScript( 'api' ) );
1537 $result->addContentValue(
1538 $path,
1539 'docref',
1540 trim(
1541 $this->msg( 'api-usage-docref', $link )->inLanguage( $formatter->getLanguage() )->text()
1542 . ' '
1543 . $this->msg( 'api-usage-mailinglist-ref' )->inLanguage( $formatter->getLanguage() )->text()
1544 )
1545 );
1546 } elseif ( $config->get( MainConfigNames::ShowExceptionDetails ) ) {
1547 $result->addContentValue(
1548 $path,
1549 'trace',
1550 $this->msg( 'api-exception-trace',
1551 get_class( $e ),
1552 $e->getFile(),
1553 $e->getLine(),
1554 MWExceptionHandler::getRedactedTraceAsString( $e )
1555 )->inLanguage( $formatter->getLanguage() )->text()
1556 );
1557 }
1558
1559 // Add the id and such
1560 $this->addRequestedFields( [ 'servedby' ] );
1561
1562 return array_keys( $errorCodes );
1563 }
1564
1570 protected function addRequestedFields( $force = [] ) {
1571 $result = $this->getResult();
1572
1573 $requestid = $this->getParameter( 'requestid' );
1574 if ( $requestid !== null ) {
1575 $result->addValue( null, 'requestid', $requestid, ApiResult::NO_SIZE_CHECK );
1576 }
1577
1578 if ( $this->getConfig()->get( MainConfigNames::ShowHostnames ) && (
1579 in_array( 'servedby', $force, true ) || $this->getParameter( 'servedby' )
1580 ) ) {
1581 $result->addValue( null, 'servedby', wfHostname(), ApiResult::NO_SIZE_CHECK );
1582 }
1583
1584 if ( $this->getParameter( 'curtimestamp' ) ) {
1585 $result->addValue( null, 'curtimestamp', wfTimestamp( TS::ISO_8601 ), ApiResult::NO_SIZE_CHECK );
1586 }
1587
1588 if ( $this->getParameter( 'responselanginfo' ) ) {
1589 $result->addValue(
1590 null,
1591 'uselang',
1592 $this->getLanguage()->getCode(),
1594 );
1595 $result->addValue(
1596 null,
1597 'errorlang',
1598 $this->getErrorFormatter()->getLanguage()->getCode(),
1600 );
1601 }
1602 }
1603
1608 protected function setupExecuteAction() {
1609 $this->addRequestedFields();
1610
1611 $params = $this->extractRequestParams();
1612 $this->mAction = $params['action'];
1613
1614 return $params;
1615 }
1616
1622 protected function setupModule() {
1623 // Instantiate the module requested by the user
1624 $module = $this->mModuleMgr->getModule( $this->mAction, 'action' );
1625 if ( $module === null ) {
1626 // Probably can't happen
1627 // @codeCoverageIgnoreStart
1628 $this->dieWithError(
1629 [ 'apierror-unknownaction', wfEscapeWikiText( $this->mAction ) ],
1630 'unknown_action'
1631 );
1632 // @codeCoverageIgnoreEnd
1633 }
1634 $moduleParams = $module->extractRequestParams();
1635
1636 // Check token, if necessary
1637 if ( $module->needsToken() === true ) {
1638 throw new LogicException(
1639 "Module '{$module->getModuleName()}' must be updated for the new token handling. " .
1640 'See documentation for ApiBase::needsToken for details.'
1641 );
1642 }
1643 if ( $module->needsToken() ) {
1644 if ( !$module->mustBePosted() ) {
1645 throw new LogicException(
1646 "Module '{$module->getModuleName()}' must require POST to use tokens."
1647 );
1648 }
1649
1650 if ( !isset( $moduleParams['token'] ) ) {
1651 // Probably can't happen
1652 // @codeCoverageIgnoreStart
1653 $module->dieWithError( [ 'apierror-missingparam', 'token' ] );
1654 // @codeCoverageIgnoreEnd
1655 }
1656
1657 $module->requirePostedParameters( [ 'token' ] );
1658
1659 if ( !$module->validateToken( $moduleParams['token'], $moduleParams ) ) {
1660 $module->dieWithError( 'apierror-badtoken' );
1661 }
1662 }
1663
1664 // @phan-suppress-next-line PhanTypeMismatchReturnNullable T240141
1665 return $module;
1666 }
1667
1671 private function getMaxLag() {
1672 $services = MediaWikiServices::getInstance();
1673 $dbLag = $services->getDBLoadBalancer()->getMaxLag();
1674 $lagInfo = [
1675 'host' => $dbLag[0],
1676 'lag' => $dbLag[1],
1677 'type' => 'db'
1678 ];
1679
1680 $jobQueueLagFactor =
1681 $this->getConfig()->get( MainConfigNames::JobQueueIncludeInMaxLagFactor );
1682 if ( $jobQueueLagFactor ) {
1683 // Turn total number of jobs into seconds by using the configured value
1684 $totalJobs = array_sum( $services->getJobQueueGroup()->getQueueSizes() );
1685 $jobQueueLag = $totalJobs / (float)$jobQueueLagFactor;
1686 if ( $jobQueueLag > $lagInfo['lag'] ) {
1687 $lagInfo = [
1688 'host' => wfHostname(), // XXX: Is there a better value that could be used?
1689 'lag' => $jobQueueLag,
1690 'type' => 'jobqueue',
1691 'jobs' => $totalJobs,
1692 ];
1693 }
1694 }
1695
1696 $this->getHookRunner()->onApiMaxLagInfo( $lagInfo );
1697
1698 return $lagInfo;
1699 }
1700
1707 protected function checkMaxLag( $module, $params ) {
1708 if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
1709 $maxLag = $params['maxlag'];
1710 $lagInfo = $this->getMaxLag();
1711 if ( $lagInfo['lag'] > $maxLag ) {
1712 $response = $this->getRequest()->response();
1713
1714 $response->header( 'Retry-After: ' . max( (int)$maxLag, 5 ) );
1715 $response->header( 'X-Database-Lag: ' . (int)$lagInfo['lag'] );
1716
1717 if ( $this->getConfig()->get( MainConfigNames::ShowHostnames ) ) {
1718 $this->dieWithError(
1719 [ 'apierror-maxlag', $lagInfo['lag'], $lagInfo['host'] ],
1720 'maxlag',
1721 $lagInfo
1722 );
1723 }
1724
1725 $this->dieWithError( [ 'apierror-maxlag-generic', $lagInfo['lag'] ], 'maxlag', $lagInfo );
1726 }
1727 }
1728
1729 return true;
1730 }
1731
1753 protected function checkConditionalRequestHeaders( $module ) {
1754 if ( $this->mInternalMode ) {
1755 // No headers to check in internal mode
1756 return true;
1757 }
1758
1759 if ( $this->getRequest()->getMethod() !== 'GET' && $this->getRequest()->getMethod() !== 'HEAD' ) {
1760 // Don't check POSTs
1761 return true;
1762 }
1763
1764 $return304 = false;
1765
1766 $ifNoneMatch = array_diff(
1767 $this->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST ) ?: [],
1768 [ '' ]
1769 );
1770 if ( $ifNoneMatch ) {
1771 // @phan-suppress-next-line PhanImpossibleTypeComparison
1772 if ( $ifNoneMatch === [ '*' ] ) {
1773 // API responses always "exist"
1774 $etag = '*';
1775 } else {
1776 $etag = $module->getConditionalRequestData( 'etag' );
1777 }
1778 }
1779 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $etag is declared when $ifNoneMatch is true
1780 if ( $ifNoneMatch && $etag !== null ) {
1781 $test = str_starts_with( $etag, 'W/' ) ? substr( $etag, 2 ) : $etag;
1782 $match = array_map( static function ( $s ) {
1783 return str_starts_with( $s, 'W/' ) ? substr( $s, 2 ) : $s;
1784 }, $ifNoneMatch );
1785 $return304 = in_array( $test, $match, true );
1786 } else {
1787 $value = trim( $this->getRequest()->getHeader( 'If-Modified-Since' ) );
1788
1789 // Some old browsers sends sizes after the date, like this:
1790 // Wed, 20 Aug 2003 06:51:19 GMT; length=5202
1791 // Ignore that.
1792 $i = strpos( $value, ';' );
1793 if ( $i !== false ) {
1794 $value = trim( substr( $value, 0, $i ) );
1795 }
1796
1797 if ( $value !== '' ) {
1798 try {
1799 $ts = new ConvertibleTimestamp( $value );
1800 if (
1801 // RFC 7231 IMF-fixdate
1802 $ts->getTimestamp( TS::RFC2822 ) === $value ||
1803 // RFC 850
1804 $ts->format( 'l, d-M-y H:i:s' ) . ' GMT' === $value ||
1805 // asctime (with and without space-padded day)
1806 $ts->format( 'D M j H:i:s Y' ) === $value ||
1807 $ts->format( 'D M j H:i:s Y' ) === $value
1808 ) {
1809 $config = $this->getConfig();
1810 $lastMod = $module->getConditionalRequestData( 'last-modified' );
1811 if ( $lastMod !== null ) {
1812 // Mix in some MediaWiki modification times
1813 $modifiedTimes = [
1814 'page' => $lastMod,
1815 'user' => $this->getUser()->getTouched(),
1816 'epoch' => $config->get( MainConfigNames::CacheEpoch ),
1817 ];
1818
1819 if ( $config->get( MainConfigNames::UseCdn ) ) {
1820 // T46570: the core page itself may not change, but resources might
1821 $modifiedTimes['sepoch'] = wfTimestamp(
1822 TS::MW, time() - $config->get( MainConfigNames::CdnMaxAge )
1823 );
1824 }
1825 $this->getHookRunner()->onOutputPageCheckLastModified( $modifiedTimes, $this->getOutput() );
1826 $lastMod = max( $modifiedTimes );
1827 $return304 = wfTimestamp( TS::MW, $lastMod ) <= $ts->getTimestamp( TS::MW );
1828 }
1829 }
1830 } catch ( TimestampException ) {
1831 // Invalid timestamp, ignore it
1832 }
1833 }
1834 }
1835
1836 if ( $return304 ) {
1837 $this->getRequest()->response()->statusHeader( 304 );
1838
1839 // Avoid outputting the compressed representation of a zero-length body
1840 AtEase::suppressWarnings();
1841 ini_set( 'zlib.output_compression', 0 );
1842 AtEase::restoreWarnings();
1843 wfResetOutputBuffers( false );
1844
1845 return false;
1846 }
1847
1848 return true;
1849 }
1850
1855 protected function checkExecutePermissions( $module ) {
1856 $user = $this->getUser();
1857 if ( $module->isReadMode() && !$this->getPermissionManager()->isEveryoneAllowed( 'read' ) &&
1858 !$this->getAuthority()->isAllowed( 'read' )
1859 ) {
1860 $this->dieWithError( 'apierror-readapidenied' );
1861 }
1862
1863 if ( $module->isWriteMode() ) {
1864 if ( !$this->mEnableWrite ) {
1865 $this->dieWithError( 'apierror-noapiwrite' );
1866 } elseif ( $this->getRequest()->getHeader( 'Promise-Non-Write-API-Action' ) ) {
1867 $this->dieWithError( 'apierror-promised-nonwrite-api' );
1868 }
1869
1870 $this->checkReadOnly( $module );
1871 }
1872
1873 // Allow extensions to stop execution for arbitrary reasons.
1874 // TODO: change hook to accept Authority
1875 $message = 'hookaborted';
1876 if ( !$this->getHookRunner()->onApiCheckCanExecute( $module, $user, $message ) ) {
1877 $this->dieWithError( $message );
1878 }
1879 }
1880
1885 protected function checkReadOnly( $module ) {
1886 if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
1887 $this->dieReadOnly();
1888 }
1889
1890 if ( $module->isWriteMode()
1891 && $this->getUser()->isBot()
1892 && MediaWikiServices::getInstance()->getDBLoadBalancer()->hasReplicaServers()
1893 ) {
1894 $this->checkBotReadOnly();
1895 }
1896 }
1897
1901 private function checkBotReadOnly() {
1902 // Figure out how many servers have passed the lag threshold
1903 $numLagged = 0;
1904 $lagLimit = $this->getConfig()->get( MainConfigNames::APIMaxLagThreshold );
1905 $laggedServers = [];
1906 $loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
1907 foreach ( $loadBalancer->getLagTimes() as $serverIndex => $lag ) {
1908 if ( $lag > $lagLimit ) {
1909 ++$numLagged;
1910 $laggedServers[] = $loadBalancer->getServerName( $serverIndex ) . " ({$lag}s)";
1911 }
1912 }
1913
1914 // If a majority of replica DBs are too lagged then disallow writes
1915 $replicaCount = $loadBalancer->getServerCount() - 1;
1916 if ( $numLagged >= ceil( $replicaCount / 2 ) ) {
1917 $laggedServers = implode( ', ', $laggedServers );
1918 wfDebugLog(
1919 'api-readonly', // Deprecate this channel in favor of api-warning?
1920 "Api request failed as read only because the following DBs are lagged: $laggedServers"
1921 );
1922 LoggerFactory::getInstance( 'api-warning' )->warning(
1923 "Api request failed as read only because the following DBs are lagged: {laggeddbs}", [
1924 'laggeddbs' => $laggedServers,
1925 ]
1926 );
1927
1928 $this->dieWithError(
1929 'readonly_lag',
1930 'readonly',
1931 [ 'readonlyreason' => "Waiting for $numLagged lagged database(s)" ]
1932 );
1933 }
1934 }
1935
1940 protected function checkAsserts( $params ) {
1941 if ( isset( $params['assert'] ) ) {
1942 $user = $this->getUser();
1943 switch ( $params['assert'] ) {
1944 case 'anon':
1945 if ( $user->isRegistered() ) {
1946 $this->dieWithError( 'apierror-assertanonfailed' );
1947 }
1948 break;
1949 case 'user':
1950 if ( !$user->isRegistered() ) {
1951 $this->dieWithError( 'apierror-assertuserfailed' );
1952 }
1953 break;
1954 case 'bot':
1955 if ( !$this->getAuthority()->isAllowed( 'bot' ) ) {
1956 $this->dieWithError( 'apierror-assertbotfailed' );
1957 }
1958 break;
1959 }
1960 }
1961 if ( isset( $params['assertuser'] ) ) {
1962 // TODO inject stuff, see T265644
1963 $assertUser = MediaWikiServices::getInstance()->getUserFactory()
1964 ->newFromName( $params['assertuser'], UserRigorOptions::RIGOR_NONE );
1965 if ( !$assertUser || !$this->getUser()->equals( $assertUser ) ) {
1966 $this->dieWithError(
1967 [ 'apierror-assertnameduserfailed', wfEscapeWikiText( $params['assertuser'] ) ]
1968 );
1969 }
1970 }
1971 }
1972
1978 protected function setupExternalResponse( $module, $params ) {
1979 $validMethods = [ 'GET', 'HEAD', 'POST', 'OPTIONS' ];
1980 $request = $this->getRequest();
1981
1982 if ( !in_array( $request->getMethod(), $validMethods ) ) {
1983 $this->dieWithError( 'apierror-invalidmethod', null, null, 405 );
1984 }
1985
1986 if ( !$request->wasPosted() && $module->mustBePosted() ) {
1987 // Module requires POST. GET request might still be allowed
1988 // if $wgDebugApi is true, otherwise fail.
1989 $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $this->mAction ] );
1990 }
1991
1992 if ( $request->wasPosted() ) {
1993 if ( !$request->getHeader( 'Content-Type' ) ) {
1994 $this->addDeprecation(
1995 'apiwarn-deprecation-post-without-content-type', 'post-without-content-type'
1996 );
1997 }
1998 $contentLength = $request->getHeader( 'Content-Length' );
1999 $maxPostSize = wfShorthandToInteger( ini_get( 'post_max_size' ), 0 );
2000 if ( $maxPostSize && $contentLength > $maxPostSize ) {
2001 $this->dieWithError(
2002 [ 'apierror-http-contenttoolarge', Message::sizeParam( $maxPostSize ) ],
2003 null, null, 413
2004 );
2005 }
2006 }
2007
2008 // See if custom printer is used
2009 $this->mPrinter = $module->getCustomPrinter() ??
2010 // Create an appropriate printer if not set
2011 $this->createPrinterByName( $params['format'] );
2012
2013 if ( $request->getProtocol() === 'http' &&
2014 (
2015 $this->getConfig()->get( MainConfigNames::ForceHTTPS ) ||
2016 $request->getSession()->shouldForceHTTPS() ||
2017 $this->getUser()->requiresHTTPS()
2018 )
2019 ) {
2020 $this->addDeprecation( 'apiwarn-deprecation-httpsexpected', 'https-expected' );
2021 }
2022 }
2023
2027 protected function executeAction() {
2028 $params = $this->setupExecuteAction();
2029
2030 // Check asserts early so e.g. errors in parsing a module's parameters due to being
2031 // logged out don't override the client's intended "am I logged in?" check.
2032 $this->checkAsserts( $params );
2033
2034 $module = $this->setupModule();
2035 $this->mModule = $module;
2036
2037 if ( !$this->mInternalMode ) {
2038 ProfilingContext::singleton()->init( MW_ENTRY_POINT, $module->getModuleName() );
2039 $this->setRequestExpectations( $module );
2040 }
2041
2042 $this->checkExecutePermissions( $module );
2043
2044 if ( !$this->checkMaxLag( $module, $params ) ) {
2045 return;
2046 }
2047
2048 if ( !$this->checkConditionalRequestHeaders( $module ) ) {
2049 return;
2050 }
2051
2052 if ( !$this->mInternalMode ) {
2053 $this->setupExternalResponse( $module, $params );
2054 }
2055
2056 $scope = LoggerFactory::getContext()->addScoped( [
2057 'context.api_module_name' => $module->getModuleName(),
2058 'context.api_client_useragent' => $this->getUserAgent(),
2059 ] );
2060 $module->execute();
2061 ScopedCallback::consume( $scope );
2062 $this->getHookRunner()->onAPIAfterExecute( $module );
2063
2064 $this->reportUnusedParams();
2065
2066 if ( !$this->mInternalMode ) {
2067 MWDebug::appendDebugInfoToApiResult( $this->getContext(), $this->getResult() );
2068
2069 $this->printResult();
2070 }
2071 }
2072
2076 protected function setRequestExpectations( ApiBase $module ) {
2077 $request = $this->getRequest();
2078
2079 $trxLimits = $this->getConfig()->get( MainConfigNames::TrxProfilerLimits );
2080 $trxProfiler = Profiler::instance()->getTransactionProfiler();
2081 $trxProfiler->setLogger( LoggerFactory::getInstance( 'rdbms' ) );
2082 $trxProfiler->setStatsFactory( MediaWikiServices::getInstance()->getStatsFactory() );
2083 $trxProfiler->setRequestMethod( $request->getMethod() );
2084 if ( $request->hasSafeMethod() ) {
2085 $trxProfiler->setExpectations( $trxLimits['GET'], __METHOD__ );
2086 } elseif ( $request->wasPosted() && !$module->isWriteMode() ) {
2087 $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
2088 } else {
2089 $trxProfiler->setExpectations( $trxLimits['POST'], __METHOD__ );
2090 }
2091 }
2092
2098 protected function logRequest( $time, ?Throwable $e = null ) {
2099 $request = $this->getRequest();
2100
2101 $user = $this->getUser();
2102 $performer = [
2103 'user_text' => $user->getName(),
2104 ];
2105 if ( $user->isRegistered() ) {
2106 $performer['user_id'] = $user->getId();
2107 }
2108 $logCtx = [
2109 // https://gerrit.wikimedia.org/g/mediawiki/event-schemas/+/master/jsonschema/mediawiki/api/request
2110 '$schema' => '/mediawiki/api/request/1.0.0',
2111 'meta' => [
2112 'request_id' => WebRequest::getRequestId(),
2114 ->getGlobalIdGenerator()->newUUIDv4(),
2115 'domain' => $this->getConfig()->get( MainConfigNames::ServerName ),
2116 // If using the EventBus extension (as intended) with this log channel,
2117 // this stream name will map to a Kafka topic.
2118 'stream' => 'mediawiki.api-request'
2119 ],
2120 'http' => [
2121 'method' => $request->getMethod(),
2122 'client_ip' => $request->getIP()
2123 ],
2124 'performer' => $performer,
2125 'database' => WikiMap::getCurrentWikiDbDomain()->getId(),
2126 'backend_time_ms' => (int)round( $time * 1000 ),
2127 ];
2128
2129 // If set, these headers will be logged in http.request_headers.
2130 $httpRequestHeadersToLog = [ 'accept-language', 'referer', 'user-agent' ];
2131 foreach ( $httpRequestHeadersToLog as $header ) {
2132 if ( $request->getHeader( $header ) ) {
2133 // Set the header in http.request_headers
2134 $logCtx['http']['request_headers'][$header] = $request->getHeader( $header );
2135 }
2136 }
2137
2138 if ( $e ) {
2139 $logCtx['api_error_codes'] = [];
2140 foreach ( $this->errorMessagesFromException( $e ) as $msg ) {
2141 $logCtx['api_error_codes'][] = $msg->getApiCode();
2142 }
2143 }
2144
2145 // Construct space separated message for 'api' log channel
2146 $msg = "API {$request->getMethod()} " .
2147 wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
2148 " {$logCtx['http']['client_ip']} " .
2149 "T={$logCtx['backend_time_ms']}ms";
2150
2151 $sensitive = array_fill_keys( $this->getSensitiveParams(), true );
2152 foreach ( $this->getParamsUsed() as $name ) {
2153 $value = $request->getVal( $name );
2154 if ( $value === null ) {
2155 continue;
2156 }
2157
2158 if ( isset( $sensitive[$name] ) ) {
2159 $value = '[redacted]';
2160 $encValue = '[redacted]';
2161 } elseif ( strlen( $value ) > 256 ) {
2162 $value = substr( $value, 0, 256 );
2163 $encValue = $this->encodeRequestLogValue( $value ) . '[...]';
2164 } else {
2165 $encValue = $this->encodeRequestLogValue( $value );
2166 }
2167
2168 $logCtx['params'][$name] = $value;
2169 $msg .= " {$name}={$encValue}";
2170 }
2171
2172 // Log an unstructured message to the api channel.
2173 wfDebugLog( 'api', $msg, 'private' );
2174
2175 // The api-request channel a structured data log channel.
2176 wfDebugLog( 'api-request', '', 'private', $logCtx );
2177 }
2178
2184 protected function encodeRequestLogValue( $s ) {
2185 static $table = [];
2186 if ( !$table ) {
2187 $chars = ';@$!*(),/:';
2188 $numChars = strlen( $chars );
2189 for ( $i = 0; $i < $numChars; $i++ ) {
2190 $table[rawurlencode( $chars[$i] )] = $chars[$i];
2191 }
2192 }
2193
2194 return strtr( rawurlencode( $s ), $table );
2195 }
2196
2201 protected function getParamsUsed() {
2202 return array_keys( $this->mParamsUsed );
2203 }
2204
2209 public function markParamsUsed( $params ) {
2210 $this->mParamsUsed += array_fill_keys( (array)$params, true );
2211 }
2212
2218 protected function getSensitiveParams() {
2219 return array_keys( $this->mParamsSensitive );
2220 }
2221
2231 public function markParamsSensitive( $params ) {
2232 $this->mParamsSensitive += array_fill_keys( (array)$params, true );
2233 }
2234
2241 public function getVal( $name, $default = null ) {
2242 $this->mParamsUsed[$name] = true;
2243
2244 $ret = $this->getRequest()->getVal( $name );
2245 if ( $ret === null ) {
2246 if ( $this->getRequest()->getArray( $name ) !== null ) {
2247 // See T12262 for why we don't just implode( '|', ... ) the
2248 // array.
2249 $this->addWarning( [ 'apiwarn-unsupportedarray', $name ] );
2250 }
2251 $ret = $default;
2252 }
2253 return $ret;
2254 }
2255
2262 public function getCheck( $name ) {
2263 $this->mParamsUsed[$name] = true;
2264 return $this->getRequest()->getCheck( $name );
2265 }
2266
2274 public function getUpload( $name ) {
2275 $this->mParamsUsed[$name] = true;
2276
2277 return $this->getRequest()->getUpload( $name );
2278 }
2279
2284 protected function reportUnusedParams() {
2285 $paramsUsed = $this->getParamsUsed();
2286 $allParams = $this->getRequest()->getValueNames();
2287
2288 if ( !$this->mInternalMode ) {
2289 // Printer has not yet executed; don't warn that its parameters are unused
2290 $printerParams = $this->mPrinter->encodeParamName(
2291 array_keys( $this->mPrinter->getFinalParams() ?: [] )
2292 );
2293 $unusedParams = array_diff( $allParams, $paramsUsed, $printerParams );
2294 } else {
2295 $unusedParams = array_diff( $allParams, $paramsUsed );
2296 }
2297
2298 if ( count( $unusedParams ) ) {
2299 $this->addWarning( [
2300 'apierror-unrecognizedparams',
2301 // Do not use `wfEscapeWikiText( ... )` here for compatibility with PHP <8.1.4
2302 // https://gerrit.wikimedia.org/r/c/mediawiki/core/+/1160800/comment/92e67687_ab221188/
2303 Message::listParam( array_map( 'wfEscapeWikiText', $unusedParams ), ListType::COMMA ),
2304 count( $unusedParams )
2305 ] );
2306 }
2307 }
2308
2314 protected function printResult( $httpCode = 0 ) {
2315 if ( $this->getConfig()->get( MainConfigNames::DebugAPI ) !== false ) {
2316 $this->addWarning( 'apiwarn-wgdebugapi' );
2317 }
2318
2319 $printer = $this->mPrinter;
2320 $printer->initPrinter( false );
2321 if ( $httpCode ) {
2322 $printer->setHttpStatus( $httpCode );
2323 }
2324 $printer->execute();
2325 $printer->closePrinter();
2326 }
2327
2331 public function isReadMode() {
2332 return false;
2333 }
2334
2340 public function getAllowedParams() {
2341 return [
2342 'action' => [
2343 ParamValidator::PARAM_DEFAULT => 'help',
2344 ParamValidator::PARAM_TYPE => 'submodule',
2345 ],
2346 'format' => [
2347 ParamValidator::PARAM_DEFAULT => self::API_DEFAULT_FORMAT,
2348 ParamValidator::PARAM_TYPE => 'submodule',
2349 ],
2350 'maxlag' => [
2351 ParamValidator::PARAM_TYPE => 'integer'
2352 ],
2353 'smaxage' => [
2354 ParamValidator::PARAM_TYPE => 'integer',
2355 ParamValidator::PARAM_DEFAULT => 0,
2356 IntegerDef::PARAM_MIN => 0,
2357 ],
2358 'maxage' => [
2359 ParamValidator::PARAM_TYPE => 'integer',
2360 ParamValidator::PARAM_DEFAULT => 0,
2361 IntegerDef::PARAM_MIN => 0,
2362 ],
2363 'assert' => [
2364 ParamValidator::PARAM_TYPE => [ 'anon', 'user', 'bot' ]
2365 ],
2366 'assertuser' => [
2367 ParamValidator::PARAM_TYPE => 'user',
2368 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'temp' ],
2369 ],
2370 'requestid' => null,
2371 'servedby' => false,
2372 'curtimestamp' => false,
2373 'responselanginfo' => false,
2374 'origin' => null,
2375 'crossorigin' => false,
2376 'uselang' => [
2377 ParamValidator::PARAM_DEFAULT => self::API_DEFAULT_USELANG,
2378 ],
2379 'variant' => null,
2380 'errorformat' => [
2381 ParamValidator::PARAM_TYPE => [ 'plaintext', 'wikitext', 'html', 'raw', 'none', 'bc' ],
2382 ParamValidator::PARAM_DEFAULT => 'bc',
2384 ],
2385 'errorlang' => [
2386 ParamValidator::PARAM_DEFAULT => 'uselang',
2387 ],
2388 'errorsuselocal' => [
2389 ParamValidator::PARAM_DEFAULT => false,
2390 ],
2391 ];
2392 }
2393
2395 protected function getExamplesMessages() {
2396 return [
2397 'action=help'
2398 => 'apihelp-help-example-main',
2399 'action=help&recursivesubmodules=1'
2400 => 'apihelp-help-example-recursive',
2401 ];
2402 }
2403
2408 public function modifyHelp( array &$help, array $options, array &$tocData ) {
2409 // Wish PHP had an "array_insert_before". Instead, we have to manually
2410 // reindex the array to get 'permissions' in the right place.
2411 $oldHelp = $help;
2412 $help = [];
2413 foreach ( $oldHelp as $k => $v ) {
2414 if ( $k === 'submodules' ) {
2415 $help['permissions'] = '';
2416 }
2417 $help[$k] = $v;
2418 }
2419 $help['datatypes'] = '';
2420 $help['templatedparams'] = '';
2421 $help['credits'] = '';
2422
2423 // Fill 'permissions'
2424 $help['permissions'] .= Html::openElement( 'div',
2425 [ 'class' => [ 'apihelp-block', 'apihelp-permissions' ] ] );
2426 $m = $this->msg( 'api-help-permissions' );
2427 if ( !$m->isDisabled() ) {
2428 $help['permissions'] .= Html::rawElement( 'div', [ 'class' => 'apihelp-block-head' ],
2429 $m->numParams( count( self::RIGHTS_MAP ) )->parse()
2430 );
2431 }
2432 $help['permissions'] .= Html::openElement( 'dl' );
2433 // TODO inject stuff, see T265644
2434 $groupPermissionsLookup = MediaWikiServices::getInstance()->getGroupPermissionsLookup();
2435 foreach ( self::RIGHTS_MAP as $right => $rightMsg ) {
2436 $help['permissions'] .= Html::element( 'dt', [], $right );
2437
2438 $rightMsg = $this->msg( $rightMsg['msg'], $rightMsg['params'] )->parse();
2439 $help['permissions'] .= Html::rawElement( 'dd', [], $rightMsg );
2440
2441 $groups = array_map( static function ( $group ) {
2442 return $group == '*' ? 'all' : $group;
2443 }, $groupPermissionsLookup->getGroupsWithPermission( $right ) );
2444
2445 $help['permissions'] .= Html::rawElement( 'dd', [],
2446 $this->msg( 'api-help-permissions-granted-to' )
2447 ->numParams( count( $groups ) )
2448 ->params( Message::listParam( $groups ) )
2449 ->parse()
2450 );
2451 }
2452 $help['permissions'] .= Html::closeElement( 'dl' );
2453 $help['permissions'] .= Html::closeElement( 'div' );
2454
2455 // Fill 'datatypes', 'templatedparams', and 'credits', if applicable
2456 if ( empty( $options['nolead'] ) ) {
2457 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset Must set when nolead is not set
2458 $level = $options['headerlevel'];
2459 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset Must set when nolead is not set
2460 $tocnumber = &$options['tocnumber'];
2461
2462 $header = $this->msg( 'api-help-datatypes-header' )->parse();
2463 $headline = Html::rawElement(
2464 'h' . min( 6, $level ),
2465 [ 'class' => 'apihelp-header', 'id' => 'main/datatypes' ],
2466 $header
2467 );
2468 $help['datatypes'] .= $headline;
2469 $help['datatypes'] .= $this->msg( 'api-help-datatypes-top' )->parseAsBlock();
2470 $help['datatypes'] .= '<dl>';
2471 foreach ( $this->getParamValidator()->knownTypes() as $type ) {
2472 $m = $this->msg( "api-help-datatype-$type" );
2473 if ( !$m->isDisabled() ) {
2474 $help['datatypes'] .= Html::element( 'dt', [ 'id' => "main/datatype/$type" ], $type );
2475 $help['datatypes'] .= Html::rawElement( 'dd', [], $m->parseAsBlock() );
2476 }
2477 }
2478 $help['datatypes'] .= '</dl>';
2479 if ( !isset( $tocData['main/datatypes'] ) ) {
2480 $tocnumber[$level]++;
2481 $tocData['main/datatypes'] = [
2482 'toclevel' => count( $tocnumber ),
2483 'level' => $level,
2484 'anchor' => 'main/datatypes',
2485 'line' => $header,
2486 'number' => implode( '.', $tocnumber ),
2487 'index' => '',
2488 ];
2489 }
2490
2491 $header = $this->msg( 'api-help-templatedparams-header' )->parse();
2492 $headline = Html::rawElement(
2493 'h' . min( 6, $level ),
2494 [ 'class' => 'apihelp-header', 'id' => 'main/templatedparams' ],
2495 $header
2496 );
2497 $help['templatedparams'] .= $headline;
2498 $help['templatedparams'] .= $this->msg( 'api-help-templatedparams' )->parseAsBlock();
2499 if ( !isset( $tocData['main/templatedparams'] ) ) {
2500 $tocnumber[$level]++;
2501 $tocData['main/templatedparams'] = [
2502 'toclevel' => count( $tocnumber ),
2503 'level' => $level,
2504 'anchor' => 'main/templatedparams',
2505 'line' => $header,
2506 'number' => implode( '.', $tocnumber ),
2507 'index' => '',
2508 ];
2509 }
2510
2511 $header = $this->msg( 'api-credits-header' )->parse();
2512 $headline = Html::rawElement(
2513 'h' . min( 6, $level ),
2514 [ 'class' => 'apihelp-header', 'id' => 'main/credits' ],
2515 $header
2516 );
2517 $help['credits'] .= $headline;
2518 $help['credits'] .= $this->msg( 'api-credits' )->useDatabase( false )->parseAsBlock();
2519 if ( !isset( $tocData['main/credits'] ) ) {
2520 $tocnumber[$level]++;
2521 $tocData['main/credits'] = [
2522 'toclevel' => count( $tocnumber ),
2523 'level' => $level,
2524 'anchor' => 'main/credits',
2525 'line' => $header,
2526 'number' => implode( '.', $tocnumber ),
2527 'index' => '',
2528 ];
2529 }
2530 }
2531 }
2532
2534 private $mCanApiHighLimits = null;
2535
2540 public function canApiHighLimits() {
2541 if ( $this->mCanApiHighLimits === null ) {
2542 $this->mCanApiHighLimits = $this->getAuthority()->isAllowed( 'apihighlimits' );
2543 }
2544
2545 return $this->mCanApiHighLimits;
2546 }
2547
2552 public function getModuleManager() {
2553 return $this->mModuleMgr;
2554 }
2555
2564 public function getUserAgent() {
2565 $agent = (string)$this->getRequest()->getHeader( 'Api-user-agent' );
2566 if ( $agent == '' ) {
2567 $agent = $this->getRequest()->getHeader( 'User-agent' );
2568 }
2569
2570 return $agent;
2571 }
2572}
2573
2580class_alias( ApiMain::class, 'ApiMain' );
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfShorthandToInteger(?string $string='', int $default=-1)
Converts shorthand byte notation to integer form.
wfHostname()
Get host name of the current machine, for use in error reporting.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
if(MW_ENTRY_POINT==='index') if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
Definition Setup.php:551
const MW_ENTRY_POINT
Definition api.php:21
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:61
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:767
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:207
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition ApiBase.php:238
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:234
isWriteMode()
Indicates whether this module requires write access to the wiki.
Definition ApiBase.php:437
Format errors and warnings in the old style, for backwards compatibility.
Formats errors and warnings for the API, and add them to the associated ApiResult.
static isValidApiCode( $code)
Test whether a code is a valid API error code.
This is the abstract base class for API formatters.
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:66
static matchRequestedHeaders( $requestedHeaders, $allowedHeaders)
Attempt to validate the value of Access-Control-Request-Headers against a list of headers that we all...
Definition ApiMain.php:1286
canApiHighLimits()
Check whether the current user is allowed to use high limits.
Definition ApiMain.php:2540
handleCORS()
Check the &origin= and/or &crossorigin= query parameters and respond appropriately.
Definition ApiMain.php:1158
getSensitiveParams()
Get the request parameters that should be considered sensitive.
Definition ApiMain.php:2218
static handleApiBeforeMainException(Throwable $e)
Handle a throwable from the ApiBeforeMain hook.
Definition ApiMain.php:1120
getErrorFormatter()
Get the ApiErrorFormatter object associated with current request.
Definition ApiMain.php:766
getStatsFactory()
Get the stats factory.
Definition ApiMain.php:808
checkConditionalRequestHeaders( $module)
Check selected RFC 7232 precondition headers.
Definition ApiMain.php:1753
checkMaxLag( $module, $params)
Check the max lag if necessary.
Definition ApiMain.php:1707
getResult()
Get the ApiResult object associated with current request.
Definition ApiMain.php:724
__construct( $context=null, $enableWrite=false, $internal=null)
Constructs an instance of ApiMain that utilizes the module and format specified by $request.
Definition ApiMain.php:599
setRequestExpectations(ApiBase $module)
Set database connection, query, and write expectations given this module request.
Definition ApiMain.php:2076
markParamsUsed( $params)
Mark parameters as used.
Definition ApiMain.php:2209
encodeRequestLogValue( $s)
Encode a value in a format suitable for a space-separated log line.
Definition ApiMain.php:2184
getAllowedParams()
See ApiBase for description.
Definition ApiMain.php:2340
setCacheControl( $directives)
Set directives (key/value pairs) for the Cache-Control header.
Definition ApiMain.php:904
checkReadOnly( $module)
Check if the DB is read-only for this user.
Definition ApiMain.php:1885
getModuleManager()
Overrides to return this instance's module manager.
Definition ApiMain.php:2552
modifyHelp(array &$help, array $options, array &$tocData)
Called from ApiHelp before the pieces are joined together and returned.This exists mainly for ApiMain...
Definition ApiMain.php:2408
isInternalMode()
Return true if the API was started by other PHP code using MediaWiki\Request\FauxRequest.
Definition ApiMain.php:715
getUserAgent()
Fetches the user agent used for this request.
Definition ApiMain.php:2564
sendCacheHeaders( $isError)
Send caching headers.
Definition ApiMain.php:1312
setupExecuteAction()
Set up for the execution.
Definition ApiMain.php:1608
setCacheMode( $mode)
Set the type of caching headers which will be sent.
Definition ApiMain.php:858
getUpload( $name)
Get a request upload, and register the fact that it was used, for logging.
Definition ApiMain.php:2274
getCheck( $name)
Get a boolean request value, and register the fact that the parameter was used, for logging.
Definition ApiMain.php:2262
reportUnusedParams()
Report unused parameters, so the client gets a hint in case it gave us parameters we don't know,...
Definition ApiMain.php:2284
substituteResultWithError(Throwable $e)
Replace the result data with the information about a throwable.
Definition ApiMain.php:1486
handleException(Throwable $e, $latency=0)
Handle a throwable as an API response.
Definition ApiMain.php:1017
execute()
Execute api request.
Definition ApiMain.php:930
setContinuationManager(?ApiContinuationManager $manager=null)
Definition ApiMain.php:780
executeAction()
Execute the actual module, without any error handling.
Definition ApiMain.php:2027
errorMessagesFromException(Throwable $e, $type='error')
Create an error message for the given throwable.
Definition ApiMain.php:1450
getPrinter()
Get the result formatter object.
Definition ApiMain.php:817
setCacheMaxAge( $maxage)
Set how long the response should be cached.
Definition ApiMain.php:826
setupModule()
Set up the module for response.
Definition ApiMain.php:1622
lacksSameOriginSecurity()
Get the security flag for the current request.
Definition ApiMain.php:732
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
Definition ApiMain.php:2395
printResult( $httpCode=0)
Print results using the current printer.
Definition ApiMain.php:2314
getVal( $name, $default=null)
Get a request value, and register the fact that it was used, for logging.
Definition ApiMain.php:2241
executeActionWithErrorHandling()
Execute an action, and in case of an error, erase whatever partial results have been accumulated,...
Definition ApiMain.php:942
createPrinterByName( $format)
Create an instance of an output formatter by its name.
Definition ApiMain.php:915
getParamsUsed()
Get the request parameters used in the course of the preceding execute() request.
Definition ApiMain.php:2201
logRequest( $time, ?Throwable $e=null)
Log the preceding request.
Definition ApiMain.php:2098
addRequestedFields( $force=[])
Add requested fields to the result.
Definition ApiMain.php:1570
checkAsserts( $params)
Check asserts of the user's rights.
Definition ApiMain.php:1940
checkExecutePermissions( $module)
Check for sufficient permissions to execute.
Definition ApiMain.php:1855
markParamsSensitive( $params)
Mark parameters as sensitive.
Definition ApiMain.php:2231
setupExternalResponse( $module, $params)
Check POST for external response and setup result printer.
Definition ApiMain.php:1978
getModule()
Get the API module object.
Definition ApiMain.php:799
static create( $msg, $code=null, ?array $data=null)
Create an IApiMessage for the message.
This class holds a list of modules and handles instantiation.
This class represents the result of the API operations.
Definition ApiResult.php:33
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don't use this unless yo...
Definition ApiResult.php:56
Exception used to abort API execution with an error.
getStatusValue()
Fetch the error status.
This wraps a bunch of the API-specific parameter validation logic.
setContext(IContextSource $context)
An IContextSource implementation which will inherit context from another source but allow individual ...
Group all the pieces relevant to the context of a request into one instance.
Debug toolbar.
Definition MWDebug.php:35
Handler class for MWExceptions.
Class to expose exceptions to the client (API bots, users, admins using CLI scripts)
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
Create PSR-3 logger objects.
A class containing constants representing the names of configuration variables.
const TrxProfilerLimits
Name constant for the TrxProfilerLimits setting, for use with Config::get()
const ForceHTTPS
Name constant for the ForceHTTPS setting, for use with Config::get()
const ServerName
Name constant for the ServerName setting, for use with Config::get()
const APIFormatModules
Name constant for the APIFormatModules setting, for use with Config::get()
const UseCdn
Name constant for the UseCdn setting, for use with Config::get()
const JobQueueIncludeInMaxLagFactor
Name constant for the JobQueueIncludeInMaxLagFactor setting, for use with Config::get()
const CrossSiteAJAXdomainExceptions
Name constant for the CrossSiteAJAXdomainExceptions setting, for use with Config::get()
const CdnMaxAge
Name constant for the CdnMaxAge setting, for use with Config::get()
const AllowedCorsHeaders
Name constant for the AllowedCorsHeaders setting, for use with Config::get()
const ShowExceptionDetails
Name constant for the ShowExceptionDetails setting, for use with Config::get()
const APIModules
Name constant for the APIModules setting, for use with Config::get()
const VaryOnXFP
Name constant for the VaryOnXFP setting, for use with Config::get()
const APIMaxLagThreshold
Name constant for the APIMaxLagThreshold setting, for use with Config::get()
const CacheEpoch
Name constant for the CacheEpoch setting, for use with Config::get()
const DebugAPI
Name constant for the DebugAPI setting, for use with Config::get()
const CrossSiteAJAXdomains
Name constant for the CrossSiteAJAXdomains setting, for use with Config::get()
const APIMaxResultSize
Name constant for the APIMaxResultSize setting, for use with Config::get()
const ShowHostnames
Name constant for the ShowHostnames setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition Message.php:492
static listParam(array $list, $type=ListType::AND)
Definition Message.php:1352
static sizeParam( $size)
Definition Message.php:1319
Type definition for user types.
Definition UserDef.php:27
Class for tracking request-level classification information for profiling/stats/logging.
WebRequest clone which takes values from a provided array.
Object to access the $_FILES array.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form,...
A class to assist with the parsing of Origin header according to the RFC 6454 https://tools....
Definition Origin.php:12
static parseHeaderList(array $headerList)
Parse an Origin header list as returned by RequestInterface::getHeader().
Definition Origin.php:28
Stub object for the global user ($wgUser) that makes it possible to change the relevant underlying ob...
static setUser( $user)
Reset the stub global user to a different "real" user object, while ensuring that any method calls on...
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:19
Profiler base class that defines the interface and some shared functionality.
Definition Profiler.php:22
static instance()
Definition Profiler.php:90
Service for formatting and validating API parameters.
Type definition for integer types.
This is the primary interface for validating metrics definitions, caching defined metrics,...
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'sodipodi'=> ' $path/sodipodi -z -w $width -f $input -e $output', 'inkscape'=> ' $path/inkscape -z -w $width -f $input -e $output', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'imgserv'=> ' $path/imgserv-wrapper -i svg -o png -w$width $input $output', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> false, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 250, 300,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'EnableSpecialMute'=> false, 'EnableUserEmailMuteList'=> false, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'BlockTargetMigrationStage' => 768, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'BlockTargetMigrationStage' => 'integer', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'RCEngines' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], 'required' => [ 'url', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Interface for objects which can provide a MediaWiki context on request.
Interface for MediaWiki-localized exceptions.
Shared interface for rigor levels when dealing with User methods.
Language name search API.
element(SerializerNode $parent, SerializerNode $node, $contents)
Helper trait for implementations \DAO.
ListType
The constants used to specify list types.
Definition ListType.php:9