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