MediaWiki master
OutputPage.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Output;
10
11use CSSJanus;
12use Exception;
13use InvalidArgumentException;
14use MediaWiki\Cache\LinkCache;
22use MediaWiki\Debug\DeprecationHelper;
25use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
56use OOUI\Element;
57use OOUI\Theme;
58use RuntimeException;
59use Wikimedia\Assert\Assert;
60use Wikimedia\Bcp47Code\Bcp47Code;
64use Wikimedia\Parsoid\Core\LinkTarget as ParsoidLinkTarget;
65use Wikimedia\Parsoid\Core\TOCData;
67use Wikimedia\RelPath;
68use Wikimedia\Timestamp\TimestampFormat as TS;
69use Wikimedia\WrappedString;
70use Wikimedia\WrappedStringList;
71
85 use ProtectedHookAccessorTrait;
86 use DeprecationHelper;
87
89 public const CSP_HEADERS = 'headers';
91 public const CSP_META = 'meta';
92
93 // Constants for getJSVars()
94 private const JS_VAR_EARLY = 1;
95 private const JS_VAR_LATE = 2;
96
97 // Core config vars that opt-in to JS_VAR_LATE.
98 // Extensions use the 'LateJSConfigVarNames' attribute instead.
99 private const CORE_LATE_JS_CONFIG_VAR_NAMES = [];
100
102 private static $oouiSetupDone = false;
103
105 protected $mMetatags = [];
106
108 protected $mLinktags = [];
109
111 protected $mCanonicalUrl = false;
112
116 private $mPageTitle = '';
117
125 private $displayTitle;
126
128 private $cacheIsFinal = false;
129
134 public $mBodytext = '';
135
137 private $mHTMLtitle = '';
138
143 private $mIsArticle = false;
144
146 private $mIsArticleRelated = true;
147
149 private $mHasCopyright = false;
150
155 private $mPrintable = false;
156
161 private $tocData;
162
167 private $mSubtitle = [];
168
170 public $mRedirect = '';
171
173 protected $mStatusCode;
174
179 protected $mLastModified = '';
180
182 private $mCategoryLinks = [];
183
185 private $mCategories = [
186 'hidden' => [],
187 'normal' => [],
188 ];
189
199 private array $mCategoryData = [];
200
206 private bool $mCategoriesSorted = true;
207
209 private array $mIndicators = [];
210
218 private $mScripts = '';
219
221 protected $mInlineStyles = '';
222
228
233 private $mHeadItems = [];
234
237
241 private $mModules = [];
242
246 private $mModuleStyles = [];
247
250
252 private $rlClient;
253
255 private $rlClientContext;
256
258 private $rlExemptStyleModules;
259
261 private $mJsConfigVars = [];
262
264 public $mRedirectCode = '';
265
267 protected $mFeedLinksAppendQuery = null;
268
274 protected $mAllowedModules = [
275 RL\Module::TYPE_COMBINED => RL\Module::ORIGIN_ALL,
276 ];
277
279 protected $mDoNothing = false;
280
281 // Parser related.
282
288 private $mParserOptions = null;
289
296 private $mFeedLinks = [];
297
303 private $mEnableClientCache = true;
304
306 private $mArticleBodyOnly = false;
307
309 protected $mCdnMaxage = 0;
311 protected $mCdnMaxageLimit = INF;
312
314 private $mRevisionId = null;
315
317 private $mRevisionIsCurrent = null;
318
320 protected $mFileVersion = null;
321
330 protected $styles = [];
331
333 private $mFollowPolicy = 'follow';
334
336 private $mRobotsOptions = [ 'max-image-preview' => 'standard' ];
337
343 private $mVaryHeader = [
344 'Accept-Encoding' => null,
345 ];
346
353 private $mRedirectedFrom = null;
354
359 private $mProperties = [];
360
364 private $mTarget = null;
365
369 private $mEnableTOC = false;
370
374 private $mOutputFlags = [];
375
379 private $copyrightUrl;
380
384 private $contentLang;
385
387 private $limitReportJSData = [];
388
390 private $contentOverrides = [];
391
393 private $contentOverrideCallbacks = [];
394
399 private $mLinkHeader = [];
400
404 private $CSP;
405
406 private string $cspOutputMode = self::CSP_HEADERS;
407
416 private ParserOutput $metadata;
417
421 private static $cacheVaryCookies = null;
422
424 private $debugMode = null;
425
431 public function __construct( IContextSource $context ) {
432 $this->deprecatePublicProperty( 'mCategoryLinks', '1.38', __CLASS__ );
433 $this->deprecatePublicProperty( 'mCategories', '1.38', __CLASS__ );
434 $this->deprecatePublicProperty( 'mIndicators', '1.38', __CLASS__ );
435 $this->deprecatePublicProperty( 'mHeadItems', '1.38', __CLASS__ );
436 $this->deprecatePublicProperty( 'mJsConfigVars', '1.38', __CLASS__ );
437 $this->deprecatePublicProperty( 'mEnableClientCache', '1.38', __CLASS__ );
438 $this->deprecatePublicProperty( 'mParserOptions', '1.44', __CLASS__ );
439 $this->setContext( $context );
440 $this->metadata = new ParserOutput( null );
441 // OutputPage default
442 $this->metadata->setPreventClickjacking( true );
443 $this->CSP = new ContentSecurityPolicy(
444 $context->getRequest()->response(),
445 $context->getConfig(),
446 $this->getHookContainer()
447 );
448 $this->metadata->setNoGallery( false );
449 $this->metadata->setNewSection( false );
450 $this->metadata->setHideNewSection( false );
451 $this->metadata->setRevisionTimestamp( null );
452 }
453
460 public function redirect( $url, $responsecode = '302' ) {
461 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
462 $this->mRedirect = str_replace( "\n", '', $url );
463 $this->mRedirectCode = (string)$responsecode;
464 }
465
471 public function getRedirect() {
472 return $this->mRedirect;
473 }
474
483 public function setCopyrightUrl( $url ) {
484 $this->copyrightUrl = $url;
485 }
486
492 public function setStatusCode( $statusCode ) {
493 $this->mStatusCode = $statusCode;
494 }
495
500 public function getMetadata(): ParserOutput {
501 // We can deprecate the redundant
502 // methods on OutputPage which simply turn around
503 // and invoke the corresponding method on the metadata
504 // ParserOutput.
505 return $this->metadata;
506 }
507
515 public function addMeta( $name, $val ) {
516 $this->mMetatags[] = [ $name, $val ];
517 }
518
525 public function getMetaTags() {
526 return $this->mMetatags;
527 }
528
536 public function addLink( array $linkarr ) {
537 $this->mLinktags[] = $linkarr;
538 }
539
546 public function getLinkTags() {
547 return $this->mLinktags;
548 }
549
555 public function setCanonicalUrl( $url ) {
556 $this->mCanonicalUrl = $url;
557 }
558
566 public function getCanonicalUrl() {
567 return $this->mCanonicalUrl;
568 }
569
578 public function addScript( $script ) {
579 $this->mScripts .= $script;
580 }
581
590 public function addScriptFile( $file, $unused = null ) {
591 $this->addScript( Html::linkedScript( $file ) );
592 }
593
601 public function addInlineScript( $script ) {
602 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n";
603 }
604
613 protected function filterModules( array $modules, $position = null,
614 $type = RL\Module::TYPE_COMBINED
615 ) {
616 $resourceLoader = $this->getResourceLoader();
617 $filteredModules = [];
618 foreach ( $modules as $val ) {
619 $module = $resourceLoader->getModule( $val );
620 if ( $module instanceof RL\Module
621 && $module->getOrigin() <= $this->getAllowedModules( $type )
622 ) {
623 $filteredModules[] = $val;
624 }
625 }
626 return $filteredModules;
627 }
628
636 public function getModules(
637 $filter = false, ...$args
638 ) {
639 // Deprecate all arguments other than the first
640 if ( count( $args ) > 0 ) {
641 wfDeprecated( __METHOD__ . ' with >1 argument', '1.44' );
642 }
643 $position = $args[0] ?? null;
644 $param = $args[1] ?? 'mModules';
645 $type = $args[2] ?? RL\Module::TYPE_COMBINED;
646 return $this->getModulesInternal(
647 $filter,
648 $param,
649 $type
650 );
651 }
652
661 private function getModulesInternal(
662 bool $filter, string $param, string $type
663 ) {
664 $modules = array_values( $this->$param );
665 return $filter
666 ? $this->filterModules( $modules, null, $type )
667 : $modules;
668 }
669
675 public function addModules( $modules ) {
676 foreach ( (array)$modules as $moduleName ) {
677 $this->mModules[$moduleName] = $moduleName;
678 }
679 }
680
688 public function getModuleStyles( $filter = false, ...$args ) {
689 // Deprecate all arguments other than the first
690 if ( count( $args ) > 0 ) {
691 wfDeprecated( __METHOD__ . ' with >1 argument', '1.44' );
692 }
693 return $this->getModulesInternal(
694 $filter, 'mModuleStyles', RL\Module::TYPE_STYLES
695 );
696 }
697
707 public function addModuleStyles( $modules ) {
708 foreach ( (array)$modules as $moduleName ) {
709 $this->mModuleStyles[$moduleName] = $moduleName;
710 }
711 }
712
716 public function getTarget() {
717 return $this->mTarget;
718 }
719
727 public function addContentOverride( $target, Content $content ) {
728 if ( !$this->contentOverrides ) {
729 // Register a callback for $this->contentOverrides on the first call
730 $this->addContentOverrideCallback( function ( $target ) {
731 $key = $target->getNamespace() . ':' . $target->getDBkey();
732 return $this->contentOverrides[$key] ?? null;
733 } );
734 }
735
736 $key = $target->getNamespace() . ':' . $target->getDBkey();
737 $this->contentOverrides[$key] = $content;
738 }
739
747 public function addContentOverrideCallback( callable $callback ) {
748 $this->contentOverrideCallbacks[] = $callback;
749 }
750
758 public function addHtmlClasses( $classes ) {
759 $this->mAdditionalHtmlClasses = array_merge( $this->mAdditionalHtmlClasses, (array)$classes );
760 }
761
765 public function getHeadItemsArray() {
766 return $this->mHeadItems;
767 }
768
782 public function addHeadItem( $name, $value ) {
783 $this->mHeadItems[$name] = $value;
784 }
785
793 public function addHeadItems( $values ) {
794 $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
795 }
796
803 public function hasHeadItem( $name ) {
804 return isset( $this->mHeadItems[$name] );
805 }
806
813 public function addBodyClasses( $classes ) {
814 $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
815 }
816
824 public function setArticleBodyOnly( $only ) {
825 $this->mArticleBodyOnly = $only;
826 }
827
833 public function getArticleBodyOnly() {
834 return $this->mArticleBodyOnly;
835 }
836
844 public function setProperty( $name, $value ) {
845 $this->mProperties[$name] = $value;
846 }
847
855 public function getProperty( $name ) {
856 return $this->mProperties[$name] ?? null;
857 }
858
870 public function checkLastModified( $timestamp ) {
871 if ( !$timestamp || $timestamp == '19700101000000' ) {
872 wfDebug( __METHOD__ . ': CACHE DISABLED, NO TIMESTAMP' );
873 return false;
874 }
875 $config = $this->getConfig();
876 if ( !$config->get( MainConfigNames::CachePages ) ) {
877 wfDebug( __METHOD__ . ': CACHE DISABLED' );
878 return false;
879 }
880
881 $timestamp = wfTimestamp( TS::MW, $timestamp );
882 $modifiedTimes = [
883 'page' => $timestamp,
884 'user' => $this->getUser()->getTouched(),
885 'epoch' => $config->get( MainConfigNames::CacheEpoch )
886 ];
887 if ( $config->get( MainConfigNames::UseCdn ) ) {
888 // Ensure Last-Modified is never more than "$wgCdnMaxAge" seconds in the past,
889 // because even if the wiki page hasn't been edited, other static resources may
890 // change (site configuration, default preferences, skin HTML, interface messages,
891 // URLs to other files and services) and must roll-over in a timely manner (T46570)
892 $modifiedTimes['sepoch'] = wfTimestamp(
893 TS::MW,
894 time() - $config->get( MainConfigNames::CdnMaxAge )
895 );
896 }
897 $this->getHookRunner()->onOutputPageCheckLastModified( $modifiedTimes, $this );
898
899 $maxModified = max( $modifiedTimes );
900 $this->mLastModified = wfTimestamp( TS::RFC2822, $maxModified );
901
902 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
903 if ( $clientHeader === false ) {
904 wfDebug( __METHOD__ . ': client did not send If-Modified-Since header', 'private' );
905 return false;
906 }
907
908 # IE sends sizes after the date like this:
909 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
910 # this breaks strtotime().
911 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
912
913 // Ignore timezone warning
914 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
915 $clientHeaderTime = @strtotime( $clientHeader );
916 if ( !$clientHeaderTime ) {
917 wfDebug( __METHOD__
918 . ": unable to parse the client's If-Modified-Since header: $clientHeader" );
919 return false;
920 }
921 $clientHeaderTime = wfTimestamp( TS::MW, $clientHeaderTime );
922
923 # Make debug info
924 $info = '';
925 foreach ( $modifiedTimes as $name => $value ) {
926 if ( $info !== '' ) {
927 $info .= ', ';
928 }
929 $info .= "$name=" . wfTimestamp( TS::ISO_8601, $value );
930 }
931
932 wfDebug( __METHOD__ . ': client sent If-Modified-Since: ' .
933 wfTimestamp( TS::ISO_8601, $clientHeaderTime ), 'private' );
934 wfDebug( __METHOD__ . ': effective Last-Modified: ' .
935 wfTimestamp( TS::ISO_8601, $maxModified ), 'private' );
936 if ( $clientHeaderTime < $maxModified ) {
937 wfDebug( __METHOD__ . ": STALE, $info", 'private' );
938 return false;
939 }
940
941 # Not modified
942 # Give a 304 Not Modified response code and disable body output
943 wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
944 ini_set( 'zlib.output_compression', 0 );
945 $this->getRequest()->response()->statusHeader( 304 );
946 $this->sendCacheControl();
947 $this->disable();
948
949 // Don't output a compressed blob when using ob_gzhandler;
950 // it's technically against HTTP spec and seems to confuse
951 // Firefox when the response gets split over two packets.
952 wfResetOutputBuffers( false );
953
954 return true;
955 }
956
963 public function setLastModified( $timestamp ) {
964 $this->mLastModified = wfTimestamp( TS::RFC2822, $timestamp );
965 }
966
974 public function setRobotPolicy( $policy ) {
975 $policy = Article::formatRobotPolicy( $policy );
976
977 if ( isset( $policy['index'] ) ) {
978 $this->setIndexPolicy( $policy['index'] );
979 }
980 if ( isset( $policy['follow'] ) ) {
981 $this->setFollowPolicy( $policy['follow'] );
982 }
983 }
984
991 public function getRobotPolicy() {
992 $indexPolicy = $this->getIndexPolicy();
993 return "{$indexPolicy},{$this->mFollowPolicy}";
994 }
995
1001 private function formatRobotsOptions(): string {
1002 $options = $this->mRobotsOptions;
1003 // Check if options array has any non-integer keys.
1004 if ( count( array_filter( array_keys( $options ), 'is_string' ) ) > 0 ) {
1005 // Robots meta tags can have directives that are single strings or
1006 // have parameters that should be formatted like <directive>:<setting>.
1007 // If the options keys are strings, format them accordingly.
1008 // https://developers.google.com/search/docs/advanced/robots/robots_meta_tag
1009 array_walk( $options, static function ( &$value, $key ) {
1010 $value = is_string( $key ) ? "{$key}:{$value}" : "{$value}";
1011 } );
1012 }
1013 return implode( ',', $options );
1014 }
1015
1023 public function setRobotsOptions( array $options = [] ): void {
1024 $this->mRobotsOptions = array_merge( $this->mRobotsOptions, $options );
1025 }
1026
1031 private function getRobotsContent(): string {
1032 $robotOptionString = $this->formatRobotsOptions();
1033 $robotArgs = ( $this->getIndexPolicy() === 'index' &&
1034 $this->mFollowPolicy === 'follow' ) ?
1035 [] :
1036 [
1037 $this->getIndexPolicy(),
1038 $this->mFollowPolicy,
1039 ];
1040 if ( $robotOptionString ) {
1041 $robotArgs[] = $robotOptionString;
1042 }
1043 return implode( ',', $robotArgs );
1044 }
1045
1059 public function setIndexPolicy( $policy ) {
1060 $policy = trim( $policy );
1061 if ( $policy === 'index' && $this->metadata->getIndexPolicy() === 'noindex' ) {
1062 wfDeprecated( __METHOD__ . ' with index after noindex', '1.43' );
1063 // ParserOutput::setIndexPolicy has noindex take precedence
1064 // (T16899) but the OutputPage version did not. Preserve
1065 // the behavior but deprecate it for future removal.
1066 $this->metadata->setOutputFlag( ParserOutputFlags::NO_INDEX_POLICY, false );
1067 }
1068 $this->metadata->setIndexPolicy( $policy );
1069 }
1070
1077 public function getIndexPolicy() {
1078 // Unlike ParserOutput, in OutputPage getIndexPolicy() defaults to
1079 // 'index' if unset.
1080 $policy = $this->metadata->getIndexPolicy();
1081 if ( $policy === '' ) {
1082 $policy = 'index';
1083 }
1084 return $policy;
1085 }
1086
1093 public function setFollowPolicy( $policy ) {
1094 $policy = trim( $policy );
1095 if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
1096 $this->mFollowPolicy = $policy;
1097 }
1098 }
1099
1105 public function getFollowPolicy() {
1106 return $this->mFollowPolicy;
1107 }
1108
1115 public function setHTMLTitle( $name ) {
1116 if ( $name instanceof Message ) {
1117 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
1118 } else {
1119 $this->mHTMLtitle = $name;
1120 }
1121 }
1122
1128 public function getHTMLTitle() {
1129 return $this->mHTMLtitle;
1130 }
1131
1135 public function setRedirectedFrom( PageReference $t ) {
1136 $this->mRedirectedFrom = $t;
1137 }
1138
1152 public function setPageTitle( $name ) {
1153 // This is a stronger check than a `string $name` type hint, which automatically stringifies
1154 // stringable objects such as Message when not using strict_types, and we don't want that.
1155 Assert::parameterType( 'string', $name, '$name' );
1156 $this->setPageTitleInternal( $name );
1157 }
1158
1174 public function setPageTitleMsg( Message $msg ): void {
1175 $this->setPageTitleInternal(
1176 $msg->setContext( $this->getContext() )->escaped()
1177 );
1178 }
1179
1180 private function setPageTitleInternal( string $name ): void {
1181 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
1182 # but leave "<i>foobar</i>" alone
1183 $nameWithTags = Sanitizer::removeSomeTags( $name );
1184 $this->mPageTitle = $nameWithTags;
1185
1186 # change "<i>foo&amp;bar</i>" to "foo&bar"
1187 $this->setHTMLTitle(
1188 $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
1189 ->inContentLanguage()
1190 );
1191 }
1192
1198 public function getPageTitle() {
1199 return $this->mPageTitle;
1200 }
1201
1209 public function setDisplayTitle( $html ) {
1210 $this->displayTitle = $html;
1211 }
1212
1221 public function getDisplayTitle() {
1222 $html = $this->displayTitle;
1223 if ( $html === null ) {
1224 return htmlspecialchars( $this->getTitle()->getPrefixedText(), ENT_NOQUOTES );
1225 }
1226
1227 return Sanitizer::removeSomeTags( $html );
1228 }
1229
1238 public function getUnprefixedDisplayTitle() {
1239 $service = MediaWikiServices::getInstance();
1240 $languageConverter = $service->getLanguageConverterFactory()
1241 ->getLanguageConverter( $service->getContentLanguage() );
1242 $text = $this->getDisplayTitle();
1243
1244 // Create a regexp with matching groups as placeholders for the namespace, separator and main text
1245 $pageTitleRegexp = '/^' . str_replace(
1246 preg_quote( '(.+?)', '/' ),
1247 '(.+?)',
1248 preg_quote( Parser::formatPageTitle( '(.+?)', '(.+?)', '(.+?)' ), '/' )
1249 ) . '$/';
1250 $matches = [];
1251 if ( preg_match( $pageTitleRegexp, $text, $matches ) ) {
1252 // The regexp above could be manipulated by malicious user input,
1253 // sanitize the result just in case
1254 return Sanitizer::removeSomeTags( $matches[3] );
1255 }
1256
1257 $nsPrefix = $languageConverter->convertNamespace(
1258 $this->getTitle()->getNamespace()
1259 ) . ':';
1260 $prefix = preg_quote( $nsPrefix, '/' );
1261
1262 return preg_replace( "/^$prefix/i", '', $text );
1263 }
1264
1268 public function setTitle( PageReference $t ) {
1269 $t = Title::newFromPageReference( $t );
1270
1271 // @phan-suppress-next-next-line PhanUndeclaredMethod
1272 // @fixme Not all implementations of IContextSource have this method!
1273 $this->getContext()->setTitle( $t );
1274 }
1275
1281 public function setSubtitle( $str ) {
1282 $this->clearSubtitle();
1283 $this->addSubtitle( $str );
1284 }
1285
1292 public function addSubtitle( $str ) {
1293 if ( $str instanceof Message ) {
1294 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1295 } else {
1296 $this->mSubtitle[] = $str;
1297 }
1298 }
1299
1308 public static function buildBacklinkSubtitle( PageReference $page, $query = [] ) {
1309 if ( $page instanceof PageRecord || $page instanceof Title ) {
1310 // Callers will typically have a PageRecord
1311 if ( $page->isRedirect() ) {
1312 $query['redirect'] = 'no';
1313 }
1314 } elseif ( $page->getNamespace() !== NS_SPECIAL ) {
1315 // We don't know whether it's a redirect, so add the parameter, just to be sure.
1316 $query['redirect'] = 'no';
1317 }
1318
1319 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1320 return wfMessage( 'backlinksubtitle' )
1321 ->rawParams( $linkRenderer->makeLink( $page, null, [], $query ) );
1322 }
1323
1330 public function addBacklinkSubtitle( PageReference $title, $query = [] ) {
1331 $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1332 }
1333
1337 public function clearSubtitle() {
1338 $this->mSubtitle = [];
1339 }
1340
1344 public function getSubtitle() {
1345 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1346 }
1347
1352 public function setPrintable() {
1353 $this->mPrintable = true;
1354 }
1355
1361 public function isPrintable() {
1362 return $this->mPrintable;
1363 }
1364
1368 public function disable() {
1369 $this->mDoNothing = true;
1370 }
1371
1377 public function isDisabled() {
1378 return $this->mDoNothing;
1379 }
1380
1387 public function showNewSectionLink() {
1388 wfDeprecated( __METHOD__, '1.44' );
1389 return $this->metadata->getNewSection();
1390 }
1391
1398 public function forceHideNewSectionLink() {
1399 wfDeprecated( __METHOD__, '1.44' );
1400 return $this->metadata->getHideNewSection();
1401 }
1402
1411 public function setSyndicated( $show = true ) {
1412 if ( $show ) {
1413 $this->setFeedAppendQuery( false );
1414 } else {
1415 $this->mFeedLinks = [];
1416 }
1417 }
1418
1425 protected function getAdvertisedFeedTypes() {
1426 if ( $this->getConfig()->get( MainConfigNames::Feed ) ) {
1427 return $this->getConfig()->get( MainConfigNames::AdvertisedFeedTypes );
1428 } else {
1429 return [];
1430 }
1431 }
1432
1442 public function setFeedAppendQuery( $val ) {
1443 $this->mFeedLinks = [];
1444
1445 foreach ( $this->getAdvertisedFeedTypes() as $type ) {
1446 $query = "feed=$type";
1447 if ( is_string( $val ) ) {
1448 $query .= '&' . $val;
1449 }
1450 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1451 }
1452 }
1453
1460 public function addFeedLink( $format, $href ) {
1461 if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
1462 $this->mFeedLinks[$format] = $href;
1463 }
1464 }
1465
1470 public function isSyndicated() {
1471 return count( $this->mFeedLinks ) > 0;
1472 }
1473
1478 public function getSyndicationLinks() {
1479 return $this->mFeedLinks;
1480 }
1481
1487 public function getFeedAppendQuery() {
1488 return $this->mFeedLinksAppendQuery;
1489 }
1490
1498 public function setArticleFlag( $newVal ) {
1499 $this->mIsArticle = $newVal;
1500 if ( $newVal ) {
1501 $this->mIsArticleRelated = $newVal;
1502 }
1503 }
1504
1511 public function isArticle() {
1512 return $this->mIsArticle;
1513 }
1514
1521 public function setArticleRelated( $newVal ) {
1522 $this->mIsArticleRelated = $newVal;
1523 if ( !$newVal ) {
1524 $this->mIsArticle = false;
1525 }
1526 }
1527
1533 public function isArticleRelated() {
1534 return $this->mIsArticleRelated;
1535 }
1536
1542 public function setCopyright( $hasCopyright ) {
1543 $this->mHasCopyright = $hasCopyright;
1544 }
1545
1555 public function showsCopyright() {
1556 return $this->isArticle() || $this->mHasCopyright;
1557 }
1558
1565 public function addLanguageLinks( array $newLinkArray ) {
1566 # $newLinkArray is in order of appearance on the page;
1567 # deduplicate so only the first for a given prefix is used
1568 # using code in ParserOutput (T26502)
1569 foreach ( $newLinkArray as $t ) {
1570 $this->metadata->addLanguageLink( $t );
1571 }
1572 }
1573
1583 public function setLanguageLinks( array $newLinkArray ) {
1584 wfDeprecated( __METHOD__, '1.43' );
1585 $this->metadata->clearLanguageLinks();
1586 foreach ( $newLinkArray as $l ) {
1587 $this->metadata->addLanguageLink( $l );
1588 }
1589 }
1590
1596 public function getLanguageLinks() {
1597 $result = [];
1598 foreach ( $this->metadata->getLinkList( ParserOutputLinkTypes::LANGUAGE ) as [ 'link' => $link ] ) {
1599 $ll = $link->getInterwiki() . ':' . $link->getDBkey();
1600 # language links can have fragments
1601 if ( $link->getFragment() !== '' ) {
1602 $ll .= '#' . $link->getFragment();
1603 }
1604 $result[] = $ll;
1605 }
1606 return $result;
1607 }
1608
1615 public function getNoGallery(): bool {
1616 wfDeprecated( __METHOD__, '1.44' );
1617 return $this->metadata->getNoGallery();
1618 }
1619
1625 public function addCategoryLinks( array $categories ) {
1626 if ( !$categories ) {
1627 return;
1628 }
1629
1630 $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1631
1632 # Set all the values to 'normal'.
1633 $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1634 $pageData = [];
1635
1636 # Mark hidden categories
1637 foreach ( $res as $row ) {
1638 if ( isset( $row->pp_value ) ) {
1639 $categories[$row->page_title] = 'hidden';
1640 }
1641 // Page exists, cache results
1642 if ( isset( $row->page_id ) ) {
1643 $pageData[$row->page_title] = $row;
1644 }
1645 }
1646
1647 # Add the remaining categories to the skin
1648 $services = MediaWikiServices::getInstance();
1649 $linkRenderer = $services->getLinkRenderer();
1650 $languageConverter = $services->getLanguageConverterFactory()
1651 ->getLanguageConverter( $services->getContentLanguage() );
1652 $collation = $services->getCollationFactory()->getCategoryCollation();
1653 foreach ( $categories as $category => $type ) {
1654 // array keys will cast numeric category names to ints, so cast back to string
1655 $category = (string)$category;
1656 $origcategory = $category;
1657 if ( array_key_exists( $category, $pageData ) ) {
1658 $title = Title::newFromRow( $pageData[$category] );
1659 } else {
1660 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1661 }
1662 if ( !$title ) {
1663 continue;
1664 }
1665 $languageConverter->findVariantLink( $category, $title, true );
1666
1667 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1668 continue;
1669 }
1670 $text = $languageConverter->convertHtml( $title->getText() );
1671 $link = null;
1672 $this->getHookRunner()->onOutputPageRenderCategoryLink( $this, $title->toPageIdentity(), $text, $link );
1673 if ( $link === null ) {
1674 $link = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1675 }
1676 $this->mCategoryData[] = [
1677 'sortKey' => $collation->getSortKey( $text ),
1678 'type' => $type,
1679 'title' => $title->getText(),
1680 'link' => $link,
1681 ];
1682 $this->mCategoriesSorted = false;
1683 // Setting mCategories and mCategoryLinks is redundant here,
1684 // but is needed for compatibility until mCategories and
1685 // mCategoryLinks are made private (T301020)
1686 $this->mCategories[$type][] = $title->getText();
1687 $this->mCategoryLinks[$type][] = $link;
1688 }
1689 }
1690
1695 protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1696 # Add the links to a LinkBatch
1697 $arr = [ NS_CATEGORY => $categories ];
1698 $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
1699 $lb = $linkBatchFactory->newLinkBatch();
1700 $lb->setArray( $arr );
1701
1702 # Fetch existence plus the hiddencat property
1703 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
1704 $fields = array_merge(
1705 LinkCache::getSelectFields(),
1706 [ 'pp_value' ]
1707 );
1708
1709 $res = $dbr->newSelectQueryBuilder()
1710 ->select( $fields )
1711 ->from( 'page' )
1712 ->leftJoin( 'page_props', null, [
1713 'pp_propname' => 'hiddencat',
1714 'pp_page = page_id',
1715 ] )
1716 ->where( $lb->constructSet( 'page', $dbr ) )
1717 ->caller( __METHOD__ )
1718 ->fetchResultSet();
1719
1720 # Add the results to the link cache
1721 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1722 $lb->addResultToCache( $linkCache, $res );
1723
1724 return $res;
1725 }
1726
1733 public function setCategoryLinks( array $categories ) {
1734 wfDeprecated( __METHOD__, '1.43' );
1735 $this->mCategoryLinks = [];
1736 foreach ( $this->mCategoryData as &$arr ) {
1737 // null out the 'link' entry for existing category data
1738 $arr['link'] = null;
1739 }
1740 $this->addCategoryLinks( $categories );
1741 }
1742
1752 public function getCategoryLinks() {
1753 $this->maybeSortCategories();
1754 return $this->mCategoryLinks;
1755 }
1756
1766 public function getCategories( $type = 'all' ) {
1767 $this->maybeSortCategories();
1768 if ( $type === 'all' ) {
1769 $allCategories = [];
1770 foreach ( $this->mCategories as $categories ) {
1771 $allCategories = array_merge( $allCategories, $categories );
1772 }
1773 return $allCategories;
1774 }
1775 if ( !isset( $this->mCategories[$type] ) ) {
1776 throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1777 }
1778 return $this->mCategories[$type];
1779 }
1780
1786 private function maybeSortCategories(): void {
1787 if ( $this->mCategoriesSorted ) {
1788 return;
1789 }
1790 // Check wiki configuration...
1791 $sortCategories = $this->getConfig()->get( MainConfigNames::SortedCategories );
1792 // ...but allow override with query parameter.
1793 $sortCategories = $this->getRequest()->getFuzzyBool( 'sortcat', $sortCategories );
1794 if ( $sortCategories ) {
1795 // Primary sort key is the first element of category data, but
1796 // break ties by looking at the other elements.
1797 usort( $this->mCategoryData, static function ( $a, $b ): int {
1798 return $a['type'] <=> $b['type'] ?:
1799 $a['sortKey'] <=> $b['sortKey'] ?:
1800 $a['title'] <=> $b['sortKey'] ?:
1801 $a['link'] <=> $b['link'];
1802 } );
1803 }
1804 // Remove duplicate entries
1805 $this->mCategoryData = array_values( array_unique( $this->mCategoryData, SORT_REGULAR ) );
1806
1807 // Rebuild mCategories and mCategoryLinks
1808 $this->mCategories = [
1809 'hidden' => [],
1810 'normal' => [],
1811 ];
1812 $this->mCategoryLinks = [];
1813 foreach ( $this->mCategoryData as $c ) {
1814 $this->mCategories[$c['type']][] = $c['title'];
1815 if ( $c['link'] !== null ) {
1816 // This test only needed because of ::setCategoryLinks()
1817 $this->mCategoryLinks[$c['type']][] = $c['link'];
1818 }
1819 }
1820 $this->mCategoriesSorted = true;
1821 }
1822
1837 public function setIndicators( array $indicators ) {
1838 $this->mIndicators = $indicators + $this->mIndicators;
1839 // Keep ordered by key
1840 ksort( $this->mIndicators );
1841 }
1842
1851 public function getIndicators(): array {
1852 // Note that some -- but not all -- indicators will be wrapped
1853 // with a class appropriate for user-generated wikitext content
1854 // (usually .mw-parser-output). The exceptions would be an
1855 // indicator added via ::addHelpLink() below, which adds content
1856 // which don't come from the parser and is not user-generated;
1857 // and any indicators added by extensions which may call
1858 // OutputPage::setIndicators() directly. In the latter case the
1859 // caller is responsible for wrapping any parser-generated
1860 // indicators.
1861 return $this->mIndicators;
1862 }
1863
1872 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1873 $this->addModuleStyles( 'mediawiki.helplink' );
1874 $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1875
1876 if ( $overrideBaseUrl ) {
1877 $helpUrl = $to;
1878 } else {
1879 $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1880 $helpUrl = "https://www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1881 }
1882
1883 $link = Html::rawElement(
1884 'a',
1885 [
1886 'href' => $helpUrl,
1887 'target' => '_blank',
1888 'class' => 'mw-helplink',
1889 ],
1890 Html::element( 'span', [ 'class' => 'mw-helplink-icon' ] ) . $text
1891 );
1892
1893 // See note in ::getIndicators() above -- unlike wikitext-generated
1894 // indicators which come from ParserOutput, this indicator will not
1895 // be wrapped.
1896 $this->setIndicators( [ 'mw-helplink' => $link ] );
1897 }
1898
1907 public function disallowUserJs() {
1908 $this->reduceAllowedModules(
1909 RL\Module::TYPE_SCRIPTS,
1910 RL\Module::ORIGIN_CORE_INDIVIDUAL
1911 );
1912
1913 // Site-wide styles are controlled by a config setting, see T73621
1914 // for background on why. User styles are never allowed.
1915 if ( $this->getConfig()->get( MainConfigNames::AllowSiteCSSOnRestrictedPages ) ) {
1916 $styleOrigin = RL\Module::ORIGIN_USER_SITEWIDE;
1917 } else {
1918 $styleOrigin = RL\Module::ORIGIN_CORE_INDIVIDUAL;
1919 }
1920 $this->reduceAllowedModules(
1921 RL\Module::TYPE_STYLES,
1922 $styleOrigin
1923 );
1924 }
1925
1932 public function getAllowedModules( $type ) {
1933 if ( $type == RL\Module::TYPE_COMBINED ) {
1934 return min( array_values( $this->mAllowedModules ) );
1935 } else {
1936 return $this->mAllowedModules[$type] ?? RL\Module::ORIGIN_ALL;
1937 }
1938 }
1939
1949 public function reduceAllowedModules( $type, $level ) {
1950 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1951 }
1952
1959 public function prependHTML( $text ) {
1960 $this->mBodytext = $text . $this->mBodytext;
1961 }
1962
1969 public function addHTML( $text ) {
1970 $this->mBodytext .= $text;
1971 }
1972
1982 public function addElement( $element, array $attribs = [], $contents = '' ) {
1983 $this->addHTML( Html::element( $element, $attribs, $contents ) );
1984 }
1985
1989 public function clearHTML() {
1990 $this->mBodytext = '';
1991 }
1992
1998 public function getHTML() {
1999 return $this->mBodytext;
2000 }
2001
2009 public function parserOptions() {
2010 wfDeprecated( __METHOD__, '1.44' );
2011 if ( !$this->mParserOptions ) {
2012 if ( !$this->getUser()->isSafeToLoad() ) {
2013 // Context user isn't unstubbable yet, so don't try to get a
2014 // ParserOptions for it. And don't cache this ParserOptions
2015 // either.
2016 $po = ParserOptions::newFromAnon();
2017 $po->setAllowUnsafeRawHtml( false );
2018 return $po;
2019 }
2020
2021 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
2022 $this->mParserOptions->setAllowUnsafeRawHtml( false );
2023 }
2024
2025 return $this->mParserOptions;
2026 }
2027
2034 private function internalParserOptions( bool $interface ): ParserOptions {
2035 if ( !$this->getUser()->isSafeToLoad() ) {
2036 // Context user isn't unstubbable yet, so don't try to get a
2037 // ParserOptions for it. And don't cache this ParserOptions
2038 // either.
2039 $parserOptions = ParserOptions::newFromAnon();
2040 } else {
2041 $parserOptions = ParserOptions::newFromContext( $this->getContext() );
2042 }
2043 $parserOptions->setAllowUnsafeRawHtml( false );
2044 $parserOptions->setSuppressSectionEditLinks();
2045 $parserOptions->setInterfaceMessage( $interface );
2046 return $parserOptions;
2047 }
2048
2056 public function setRevisionId( $revid ) {
2057 $val = $revid === null ? null : intval( $revid );
2058 return wfSetVar( $this->mRevisionId, $val, true );
2059 }
2060
2066 public function getRevisionId() {
2067 return $this->mRevisionId;
2068 }
2069
2074 public function setRevisionIsCurrent( bool $isCurrent ): void {
2075 $this->mRevisionIsCurrent = $isCurrent;
2076 }
2077
2084 public function isRevisionCurrent(): bool {
2085 return $this->mRevisionId == 0 || (
2086 $this->mRevisionIsCurrent ?? (
2087 $this->mRevisionId == $this->getTitle()->getLatestRevID()
2088 )
2089 );
2090 }
2091
2100 public function setRevisionTimestamp( $timestamp ) {
2101 wfDeprecated( __METHOD__, '1.44' );
2102 $previousValue = $this->metadata->getRevisionTimestamp();
2103 $this->metadata->setRevisionTimestamp( $timestamp );
2104 return $previousValue;
2105 }
2106
2114 public function getRevisionTimestamp() {
2115 return $this->metadata->getRevisionTimestamp();
2116 }
2117
2124 public function setFileVersion( $file ) {
2125 $val = null;
2126 if ( $file instanceof File && $file->exists() ) {
2127 $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
2128 }
2129 return wfSetVar( $this->mFileVersion, $val, true );
2130 }
2131
2137 public function getFileVersion() {
2138 return $this->mFileVersion;
2139 }
2140
2147 public function getTemplateIds() {
2148 $result = [];
2149 foreach (
2150 $this->metadata->getLinkList( ParserOutputLinkTypes::TEMPLATE ) as
2151 [ 'link' => $link, 'pageid' => $pageid, 'revid' => $revid ] ) {
2152 $ns = $link->getNamespace();
2153 $dbk = $link->getDBkey();
2154 $result[$ns][$dbk] = $revid;
2155 }
2156 return $result;
2157 }
2158
2165 public function getFileSearchOptions() {
2166 $result = [];
2167 foreach (
2168 $this->metadata->getLinkList( ParserOutputLinkTypes::MEDIA ) as
2169 $linkItem ) {
2170 $link = $linkItem['link'];
2171 unset( $linkItem['link'] );
2172 $result[$link->getDBkey()] = $linkItem + [
2173 'time' => null, 'sha1' => null,
2174 ];
2175 }
2176 return $result;
2177 }
2178
2193 public function addWikiTextAsInterface(
2194 $text, $linestart = true, ?PageReference $title = null
2195 ) {
2196 $title ??= $this->getTitle();
2197 if ( $title === null ) {
2198 throw new RuntimeException( 'No title in ' . __METHOD__ );
2199 }
2200 $this->addWikiTextTitleInternal( $text, $title, $linestart,
2201 $this->internalParserOptions( true ) );
2202 }
2203
2220 $wrapperClass, $text
2221 ) {
2222 wfDeprecated( __METHOD__, '1.45' );
2223 if ( $wrapperClass === '' ) {
2224 // I don't think anyone actually uses this corner case,
2225 // but if you call wrapWikiTextAsInterface with
2226 // `$wrapperClass===''` the result won't actually be
2227 // wrapped. (Since
2228 // ParserOptions::getInterfaceMessage()===true the default
2229 // 'mw-parser-output' class is suppressed; ordinarily its
2230 // presence would ensure the wrapper was created even if
2231 // $wrapperClass was empty.)
2232 wfDeprecated( __METHOD__ . ' with empty wrapper class', '1.44' );
2233 }
2234 $title = $this->getTitle();
2235 if ( $title === null ) {
2236 throw new RuntimeException( 'No title in ' . __METHOD__ );
2237 }
2238 $this->addWikiTextTitleInternal(
2239 $text,
2240 $title,
2241 true,
2242 $this->internalParserOptions( true ),
2243 $wrapperClass
2244 );
2245 }
2246
2260 public function addWikiTextAsContent(
2261 $text, $linestart = true, ?PageReference $title = null
2262 ) {
2263 $title ??= $this->getTitle();
2264 if ( !$title ) {
2265 throw new RuntimeException( 'No title in ' . __METHOD__ );
2266 }
2267 $this->addWikiTextTitleInternal( $text, $title, $linestart,
2268 $this->internalParserOptions( false ) );
2269 }
2270
2282 private function addWikiTextTitleInternal(
2283 string $text, PageReference $title, bool $linestart, ParserOptions $popts,
2284 ?string $wrapperClass = null
2285 ) {
2286 [ $parserOutput, $parserOptions ] = $this->parseInternal(
2287 $text, $title, $linestart, $popts,
2288 /*allowTOC*/ true, $wrapperClass, /*postprocess*/ false
2289 );
2290
2291 $this->addParserOutput( $parserOutput, $parserOptions, [
2292 ] );
2293 }
2294
2300 public function setTOCData( TOCData $tocData ) {
2301 $this->tocData = $tocData;
2302 }
2303
2309 public function getTOCData(): ?TOCData {
2310 return $this->tocData;
2311 }
2312
2319 public function getOutputFlag( ParserOutputFlags|string $name ): bool {
2320 if ( $name instanceof ParserOutputFlags ) {
2321 $name = $name->value;
2322 }
2323 return $this->mOutputFlags[$name] ?? false;
2324 }
2325
2331 public function setContentLangForJS( Bcp47Code $lang ): void {
2332 $this->contentLang = MediaWikiServices::getInstance()->getLanguageFactory()
2333 ->getLanguage( $lang );
2334 }
2335
2350 private function getContentLangForJS(): Language {
2351 if ( !$this->contentLang ) {
2352 // If this is not set, then we're likely not on in a request that renders page content
2353 // (e.g. ViewAction or ApiParse), but rather a different Action or SpecialPage.
2354 // In that case there isn't a main ParserOutput object to represent the page or output.
2355 // But, the skin and frontend code mostly don't make this distinction, and so we still
2356 // need to return something for mw.config.
2357 //
2358 // For historical reasons, the expectation is that:
2359 // * on a SpecialPage, we return the language for the content area just like on a
2360 // page view. SpecialPage content is localised, and so this is the user language.
2361 // * on an Action about a WikiPage, we return the language that content would have
2362 // been shown in, if this were a page view. This is generally the page language
2363 // as stored in the database, except adapted to the current user (e.g. in case of
2364 // translated pages or a language variant preference)
2365 //
2366 // This mess was centralised to here in 2023 (T341244).
2367 $title = $this->getTitle();
2368 if ( $title->isSpecialPage() ) {
2369 // Special pages render in the interface language, based on request context.
2370 // If the user's preference (or request parameter) specifies a variant,
2371 // the content may have been converted to the user's language variant.
2372 $pageLang = $this->getLanguage();
2373 } else {
2374 wfDebug( __METHOD__ . ' has to guess ParserOutput language' );
2375 // Guess what Article::getParserOutput and ParserOptions::optionsHash() would decide
2376 // on a page view:
2377 //
2378 // - Pages may have a custom page_lang set in the database,
2379 // via Title::getPageLanguage/Title::getDbPageLanguage
2380 //
2381 // - Interface messages (NS_MEDIAWIKI) render based on their subpage,
2382 // via Title::getPageLanguage/ContentHandler::getPageLanguage/MessageCache::figureMessage
2383 //
2384 // - Otherwise, pages are assumed to be in the wiki's default content language.
2385 // via Title::getPageLanguage/ContentHandler::getPageLanguage/MediaWikiServices::getContentLanguage
2386 $pageLang = $title->getPageLanguage();
2387 }
2388 if ( $title->getNamespace() !== NS_MEDIAWIKI ) {
2389 $services = MediaWikiServices::getInstance();
2390 $langConv = $services->getLanguageConverterFactory()->getLanguageConverter( $pageLang );
2391 // NOTE: LanguageConverter::getPreferredVariant inspects global RequestContext.
2392 // This usually returns $pageLang unchanged.
2393 $variant = $langConv->getPreferredVariant();
2394 if ( $pageLang->getCode() !== $variant ) {
2395 $pageLang = $services->getLanguageFactory()->getLanguage( $variant );
2396 }
2397 }
2398 $this->contentLang = $pageLang;
2399 }
2400 return $this->contentLang;
2401 }
2402
2411 public function addParserOutputMetadata( ParserOutput $parserOutput ) {
2412 // T301020 This should eventually use the standard "merge ParserOutput"
2413 // function between $parserOutput and $this->metadata.
2414 foreach (
2415 $parserOutput->getLinkList( ParserOutputLinkTypes::LANGUAGE )
2416 as $linkItem
2417 ) {
2418 $this->metadata->appendLinkList( ParserOutputLinkTypes::LANGUAGE, $linkItem );
2419 }
2420
2421 $cats = [];
2422 foreach (
2423 $parserOutput->getLinkList( ParserOutputLinkTypes::CATEGORY )
2424 as [ 'link' => $link, 'sort' => $sort ]
2425 ) {
2426 $cats[$link->getDBkey()] = $sort;
2427 }
2428 $this->addCategoryLinks( $cats );
2429
2430 // Parser-generated indicators get wrapped like other parser output.
2431 $wrapClass = $parserOutput->getWrapperDivClass();
2432 $result = [];
2433 foreach ( $parserOutput->getIndicators() as $name => $html ) {
2434 if ( $html !== '' && $wrapClass !== '' ) {
2435 $html = Html::rawElement( 'div', [ 'class' => $wrapClass ], $html );
2436 }
2437 $result[$name] = $html;
2438 }
2439 $this->setIndicators( $result );
2440
2441 $tocData = $parserOutput->getTOCData();
2442 // Do not override existing TOC data if the new one is empty (T307256#8817705)
2443 // TODO: Invent a way to merge TOCs from multiple outputs (T327429)
2444 if ( $tocData !== null && ( $this->tocData === null || count( $tocData->getSections() ) > 0 ) ) {
2445 $this->setTOCData( $tocData );
2446 }
2447
2448 // FIXME: Best practice is for OutputPage to be an accumulator, as
2449 // addParserOutputMetadata() may be called multiple times, but the
2450 // following lines overwrite any previous data. These should
2451 // be migrated to an injection pattern. (T301020, T300979)
2452 // (Note that OutputPage::getOutputFlag() also contains this
2453 // information, with flags from each $parserOutput all OR'ed together.)
2454 $this->metadata->setNewSection( $parserOutput->getNewSection() );
2455 $this->metadata->setHideNewSection( $parserOutput->getHideNewSection() );
2456 $this->metadata->setNoGallery( $parserOutput->getNoGallery() );
2457
2458 if ( !$parserOutput->isCacheable() ) {
2459 $this->disableClientCache();
2460 }
2461 $this->addHeadItems( $parserOutput->getHeadItems() );
2462 $this->addModules( $parserOutput->getModules() );
2463 $this->addModuleStyles( $parserOutput->getModuleStyles() );
2464 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
2465 if ( $parserOutput->getPreventClickjacking() ) {
2466 $this->metadata->setPreventClickjacking( true );
2467 }
2468 $scriptSrcs = $parserOutput->getExtraCSPScriptSrcs();
2469 foreach ( $scriptSrcs as $src ) {
2470 $this->getCSP()->addScriptSrc( $src );
2471 }
2472 $defaultSrcs = $parserOutput->getExtraCSPDefaultSrcs();
2473 foreach ( $defaultSrcs as $src ) {
2474 $this->getCSP()->addDefaultSrc( $src );
2475 }
2476 $styleSrcs = $parserOutput->getExtraCSPStyleSrcs();
2477 foreach ( $styleSrcs as $src ) {
2478 $this->getCSP()->addStyleSrc( $src );
2479 }
2480
2481 // If $wgImagePreconnect is true, and if the output contains images, give the user-agent
2482 // a hint about a remote hosts from which images may be served. Launched in T123582.
2483 if ( $this->getConfig()->get( MainConfigNames::ImagePreconnect ) && $parserOutput->hasImages() ) {
2484 $preconnect = [];
2485 // Optimization: Instead of processing each image, assume that wikis either serve both
2486 // foreign and local from the same remote hostname (e.g. public wikis at WMF), or that
2487 // foreign images are common enough to be worth the preconnect (e.g. private wikis).
2488 $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
2489 $repoGroup->forEachForeignRepo( static function ( $repo ) use ( &$preconnect ) {
2490 $preconnect[] = $repo->getZoneUrl( 'thumb' );
2491 } );
2492 // Consider both foreign and local repos. While LocalRepo by default uses a relative
2493 // path on the same domain, wiki farms may configure it to use a dedicated hostname.
2494 $preconnect[] = $repoGroup->getLocalRepo()->getZoneUrl( 'thumb' );
2495 foreach ( $preconnect as $url ) {
2496 $host = parse_url( $url, PHP_URL_HOST );
2497 // It is expected that file URLs are often path-only, without hostname (T317329).
2498 if ( $host ) {
2499 $this->addLink( [ 'rel' => 'preconnect', 'href' => '//' . $host ] );
2500 break;
2501 }
2502 }
2503 }
2504
2505 // Template versioning and File Search Options
2506 foreach ( [
2507 ParserOutputLinkTypes::TEMPLATE,
2508 ParserOutputLinkTypes::MEDIA,
2509 ] as $linkType ) {
2510 foreach ( $parserOutput->getLinkList( $linkType ) as $linkItem ) {
2511 $this->metadata->appendLinkList( $linkType, $linkItem );
2512 }
2513 }
2514
2515 // Enable OOUI if requested via ParserOutput
2516 if ( $parserOutput->getEnableOOUI() ) {
2517 $this->enableOOUI();
2518 }
2519
2520 // Include parser limit report
2521 // FIXME: This should append, rather than overwrite, or else this
2522 // data should be injected into the OutputPage like is done for the
2523 // other page-level things (like OutputPage::setTOCData()).
2524 if ( !$this->limitReportJSData ) {
2525 $this->limitReportJSData = $parserOutput->getLimitReportJSData();
2526 }
2527
2528 // Link flags are ignored for now, but may in the future be
2529 // used to mark individual language links.
2530 $linkFlags = [];
2531 $languageLinks = $this->getLanguageLinks();
2532 // This hook can be used to remove/replace language links
2533 $this->getHookRunner()->onLanguageLinks( $this->getTitle(), $languageLinks, $linkFlags );
2534 $this->metadata->clearLanguageLinks();
2535 foreach ( ( $languageLinks ?? [] ) as $l ) {
2536 $this->metadata->addLanguageLink( $l );
2537 }
2538
2539 $this->getHookRunner()->onOutputPageParserOutput( $this, $parserOutput );
2540
2541 // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
2542 // so that extensions may modify ParserOutput to toggle TOC.
2543 // This cannot be moved to addParserOutputText because that is not
2544 // called by EditPage for Preview.
2545
2546 // ParserOutputFlags::SHOW_TOC is used to indicate whether the TOC
2547 // should be shown (or hidden) in the output.
2548 $this->mEnableTOC = $this->mEnableTOC ||
2549 $parserOutput->getOutputFlag( ParserOutputFlags::SHOW_TOC );
2550 // Uniform handling of all boolean flags: they are OR'ed together
2551 // (See ParserOutput::collectMetadata())
2552 $flags =
2553 array_flip( $parserOutput->getAllFlags() ) +
2554 array_flip( ParserOutputFlags::values() );
2555 foreach ( $flags as $name => $ignore ) {
2556 if ( $parserOutput->getOutputFlag( $name ) ) {
2557 $this->mOutputFlags[$name] = true;
2558 }
2559 }
2560 }
2561
2562 private function getParserOutputText(
2563 ParserOutput $parserOutput,
2564 ParserOptions $parserOptions,
2565 array $poOptions
2566 ): string {
2567 // Add default options from the skin
2568 $skin = $this->getSkin();
2569 $skinOptions = $skin->getOptions();
2570 $oldText = $parserOutput->getRawText();
2571 $poOptions += [
2572 // T371022
2573 'allowClone' => false,
2574 'skin' => $skin,
2575 'injectTOC' => $skinOptions['toc'],
2576 ];
2577 $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
2578 // Note: this path absolutely expects the metadata of $parserOutput to be mutated by the pipeline,
2579 // but the raw text should not be, see T353257
2580 // TODO T371008 consider if using the Content framework makes sense instead of creating the pipeline
2581 $text = $pipeline->run(
2582 $parserOutput,
2583 // This should be the same parser options that generated
2584 // $parserOutput
2585 $parserOptions,
2586 $poOptions
2587 )->getContentHolderText();
2588 $parserOutput->setRawText( $oldText );
2589 return $text;
2590 }
2591
2602 public function addParserOutputContent( ParserOutput $parserOutput, $parserOptions = null, $poOptions = null ) {
2603 // For backward compatibility, accept $poOptions in the $parserOptions
2604 // argument. This will also trigger the deprecation warning below.
2605 if ( is_array( $parserOptions ) ) {
2606 $poOptions = $parserOptions;
2607 $parserOptions = null;
2608 }
2609 if ( $parserOptions === null ) {
2610 wfDeprecated( __METHOD__ . ' without ParserOptions argument', '1.44' );
2611 // XXX: This isn't guaranteed to be the same parser options that
2612 // generated $parserOutput.
2613 $parserOptions = $this->internalParserOptions( false );
2614 }
2615 $poOptions ??= [];
2616 $text = $this->getParserOutputText( $parserOutput, $parserOptions, $poOptions );
2617 $this->addParserOutputText( $text, $poOptions );
2618
2619 $this->addModules( $parserOutput->getModules() );
2620 $this->addModuleStyles( $parserOutput->getModuleStyles() );
2621
2622 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
2623 }
2624
2632 public function addParserOutputText( $text, $poOptions = [] ) {
2633 if ( $text instanceof ParserOutput ) {
2634 wfDeprecated( __METHOD__ . ' with ParserOutput as first arg', '1.42' );
2635 $parserOptions = $this->internalParserOptions( false );
2636 $text = $this->getParserOutputText( $text, $parserOptions, $poOptions );
2637 }
2638 $this->getHookRunner()->onOutputPageBeforeHTML( $this, $text );
2639 $this->addHTML( $text );
2640 }
2641
2650 public function addParserOutput( ParserOutput $parserOutput, $parserOptions = null, $poOptions = null ) {
2651 // For backward compatibility, accept $poOptions in the $parserOptions
2652 // argument. This will also trigger the deprecation warning below.
2653 if ( is_array( $parserOptions ) ) {
2654 $poOptions = $parserOptions;
2655 $parserOptions = null;
2656 }
2657 if ( $parserOptions === null ) {
2658 wfDeprecated( __METHOD__ . ' without ParserOptions argument', '1.44' );
2659 // XXX: This isn't guaranteed to be the same parser options that
2660 // generated $parserOutput.
2661 $parserOptions = $this->internalParserOptions( false );
2662 }
2663 $poOptions ??= [];
2664
2666 $text = $this->getParserOutputText( $parserOutput, $parserOptions, $poOptions );
2667 $this->addParserOutputMetadata( $parserOutput );
2668 $this->addParserOutputText( $text, $poOptions );
2669 }
2670
2671 public function addPostProcessedParserOutput( ParserOutput $parserOutput ) {
2672 $this->addParserOutputMetadata( $parserOutput );
2673 $this->addParserOutputText( $parserOutput->getContentHolderText() );
2674 }
2675
2681 public function addTemplate( &$template ) {
2682 $this->addHTML( $template->getHTML() );
2683 }
2684
2695 public function parseAsContent( $text, $linestart = true ) {
2696 $title = $this->getTitle();
2697 if ( $title === null ) {
2698 throw new RuntimeException( 'No title in ' . __METHOD__ );
2699 }
2700 [ $po, ] = $this->parseInternal(
2701 $text, $title, $linestart,
2702 $this->internalParserOptions( false ),
2703 /*allowTOC*/ false, /*wrapperDivClass*/ null, /*postprocess*/ true
2704 );
2705 return $po->getContentHolderText();
2706 }
2707
2719 public function parseAsInterface( $text, $linestart = true ) {
2720 $title = $this->getTitle();
2721 if ( $title === null ) {
2722 throw new RuntimeException( 'No title in ' . __METHOD__ );
2723 }
2724 [ $po, ] = $this->parseInternal(
2725 $text, $title, $linestart,
2726 $this->internalParserOptions( true ),
2727 /*allowTOC*/ false, /*wrapperDivClass*/ null, /*postprocess*/ true
2728 );
2729 return $po->getContentHolderText();
2730 }
2731
2745 public function parseInlineAsInterface( $text, $linestart = true ) {
2746 return Parser::stripOuterParagraph(
2747 $this->parseAsInterface( $text, $linestart )
2748 );
2749 }
2750
2763 private function parseInternal(
2764 string $text, PageReference $title,
2765 bool $linestart, ParserOptions $popts, bool $allowTOC, ?string $wrapperClass,
2766 bool $postprocess
2767 ) {
2768 $parserOutput = MediaWikiServices::getInstance()->getParserFactory()->getInstance()
2769 ->parse(
2770 $text, $title, $popts,
2771 $linestart, true, $this->mRevisionId
2772 );
2773
2774 // Don't include default mw-parser-output wrap class, just use our own
2775 $parserOutput->clearWrapperDivClass();
2776 if ( $wrapperClass !== null ) {
2777 $parserOutput->addWrapperDivClass( $wrapperClass );
2778 }
2779
2780 if ( !$allowTOC ) {
2781 $parserOutput->setOutputFlag( ParserOutputFlags::NO_TOC );
2782 $parserOutput->setSections( [] );
2783 }
2784
2785 if ( $postprocess ) {
2786 $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
2787 // TODO T371008 consider if using the Content framework makes sense instead of creating the pipeline
2788 $parserOutput = $pipeline->run(
2789 $parserOutput, $popts, [
2790 'userLang' => $this->getContext()->getLanguage(),
2791 ]
2792 );
2793 }
2794
2795 return [ $parserOutput, $popts ];
2796 }
2797
2803 public function setCdnMaxage( $maxage ) {
2804 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2805 }
2806
2816 public function lowerCdnMaxage( $maxage ) {
2817 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2818 $this->setCdnMaxage( $this->mCdnMaxage );
2819 }
2820
2833 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2834 $minTTL = $minTTL ?: 60;
2835 $maxTTL = $maxTTL ?: $this->getConfig()->get( MainConfigNames::CdnMaxAge );
2836
2837 if ( $mtime === null || $mtime === false ) {
2838 // entity does not exist
2839 return;
2840 }
2841
2842 $age = MWTimestamp::time() - (int)wfTimestamp( TS::UNIX, $mtime );
2843 $adaptiveTTL = max( 0.9 * $age, $minTTL );
2844 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2845
2846 $this->lowerCdnMaxage( (int)$adaptiveTTL );
2847 }
2848
2852 public function enableClientCache(): void {
2853 $this->mEnableClientCache = true;
2854 }
2855
2860 public function disableClientCache(): void {
2861 $this->mEnableClientCache = false;
2862 }
2863
2870 public function couldBePublicCached() {
2871 if ( !$this->cacheIsFinal ) {
2872 // - The entry point handles its own caching and/or doesn't use OutputPage.
2873 // (such as load.php, or MediaWiki\Rest\EntryPoint).
2874 //
2875 // - Or, we haven't finished processing the main part of the request yet
2876 // (e.g. Action::show, SpecialPage::execute), and the state may still
2877 // change via enableClientCache().
2878 return true;
2879 }
2880 // e.g. various error-type pages disable all client caching
2881 return $this->mEnableClientCache;
2882 }
2883
2893 public function considerCacheSettingsFinal() {
2894 $this->cacheIsFinal = true;
2895 }
2896
2897 private function getSessionManager(): SessionManagerInterface {
2898 return MediaWikiServices::getInstance()->getSessionManager();
2899 }
2900
2906 public function getCacheVaryCookies() {
2907 if ( self::$cacheVaryCookies === null ) {
2908 $config = $this->getConfig();
2909 self::$cacheVaryCookies = array_values( array_unique( array_merge(
2910 $this->getSessionManager()->getVaryCookies(),
2911 [
2912 'forceHTTPS',
2913 ],
2914 $config->get( MainConfigNames::CacheVaryCookies )
2915 ) ) );
2916 $this->getHookRunner()->onGetCacheVaryCookies( $this, self::$cacheVaryCookies );
2917 }
2918 return self::$cacheVaryCookies;
2919 }
2920
2927 public function haveCacheVaryCookies() {
2928 $request = $this->getRequest();
2929 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2930 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2931 wfDebug( __METHOD__ . ": found $cookieName" );
2932 return true;
2933 }
2934 }
2935 wfDebug( __METHOD__ . ': no cache-varying cookies found' );
2936 return false;
2937 }
2938
2944 public function addVaryHeader( $header ) {
2945 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2946 $this->mVaryHeader[$header] = null;
2947 }
2948 }
2949
2956 public function getVaryHeader() {
2957 // If we vary on cookies, let's make sure it's always included here too.
2958 if ( $this->getCacheVaryCookies() ) {
2959 $this->addVaryHeader( 'Cookie' );
2960 }
2961
2962 foreach ( $this->getSessionManager()->getVaryHeaders() as $header => $_ ) {
2963 $this->addVaryHeader( $header );
2964 }
2965 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2966 }
2967
2973 public function addLinkHeader( $header ) {
2974 $this->mLinkHeader[] = $header;
2975 }
2976
2982 public function getLinkHeader() {
2983 if ( !$this->mLinkHeader ) {
2984 return false;
2985 }
2986
2987 return 'Link: ' . implode( ',', $this->mLinkHeader );
2988 }
2989
2997 private function addAcceptLanguage() {
2998 $title = $this->getTitle();
2999 if ( !$title instanceof Title ) {
3000 return;
3001 }
3002
3003 $languageConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
3004 ->getLanguageConverter( $title->getPageLanguage() );
3005 if ( !$this->getRequest()->getCheck( 'variant' ) && $languageConverter->hasVariants() ) {
3006 $this->addVaryHeader( 'Accept-Language' );
3007 }
3008 }
3009
3029 public function setPreventClickjacking( bool $enable ) {
3030 $this->metadata->setPreventClickjacking( $enable );
3031 }
3032
3040 public function getPreventClickjacking() {
3041 return $this->metadata->getPreventClickjacking();
3042 }
3043
3051 public function getFrameOptions() {
3052 $config = $this->getConfig();
3053 if ( $config->get( MainConfigNames::BreakFrames ) ) {
3054 return 'DENY';
3055 } elseif (
3056 $this->metadata->getPreventClickjacking() &&
3057 $config->get( MainConfigNames::EditPageFrameOptions )
3058 ) {
3059 return $config->get( MainConfigNames::EditPageFrameOptions );
3060 }
3061 return false;
3062 }
3063
3065 private function getReportTo() {
3066 $config = $this->getConfig();
3067
3068 $expiry = $config->get( MainConfigNames::ReportToExpiry );
3069
3070 if ( !$expiry ) {
3071 return false;
3072 }
3073
3074 $endpoints = $config->get( MainConfigNames::ReportToEndpoints );
3075
3076 if ( !$endpoints ) {
3077 return false;
3078 }
3079
3080 $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
3081
3082 foreach ( $endpoints as $endpoint ) {
3083 $output['endpoints'][] = [ 'url' => $endpoint ];
3084 }
3085
3086 return json_encode( $output, JSON_UNESCAPED_SLASHES );
3087 }
3088
3089 private function getFeaturePolicyReportOnly(): string {
3090 $config = $this->getConfig();
3091
3092 $features = $config->get( MainConfigNames::FeaturePolicyReportOnly );
3093 return implode( ';', $features );
3094 }
3095
3099 public function sendCacheControl() {
3100 $response = $this->getRequest()->response();
3101 $config = $this->getConfig();
3102
3103 $this->addVaryHeader( 'Cookie' );
3104 $this->addAcceptLanguage();
3105
3106 # don't serve compressed data to clients who can't handle it
3107 # maintain different caches for logged-in users and non-logged in ones
3108 $response->header( $this->getVaryHeader() );
3109
3110 if ( $this->mEnableClientCache ) {
3111 if ( !$config->get( MainConfigNames::UseCdn ) ) {
3112 $privateReason = 'config';
3113 } elseif ( $response->hasCookies() ) {
3114 $privateReason = 'set-cookies';
3115 // The client might use methods other than cookies to appear logged-in.
3116 // E.g. HTTP headers, or query parameter tokens, OAuth, etc.
3117 } elseif ( $this->getRequest()->getSession()->isPersistent() ) {
3118 $privateReason = 'session';
3119 } elseif ( $this->isPrintable() ) {
3120 $privateReason = 'printable';
3121 } elseif ( $this->mCdnMaxage == 0 ) {
3122 $privateReason = 'no-maxage';
3123 } elseif ( $this->haveCacheVaryCookies() ) {
3124 $privateReason = 'cache-vary-cookies';
3125 } else {
3126 $privateReason = false;
3127 }
3128
3129 if ( $privateReason === false ) {
3130 # We'll purge the proxy cache for anons explicitly, but require end user agents
3131 # to revalidate against the proxy on each visit.
3132 # IMPORTANT! The CDN needs to replace the Cache-Control header with
3133 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
3134 wfDebug( __METHOD__ .
3135 ": local proxy caching; {$this->mLastModified} **", 'private' );
3136 # start with a shorter timeout for initial testing
3137 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
3138 $response->header( 'Cache-Control: ' .
3139 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
3140 } else {
3141 # We do want clients to cache if they can, but they *must* check for updates
3142 # on revisiting the page.
3143 wfDebug( __METHOD__ . ": private caching ($privateReason); {$this->mLastModified} **", 'private' );
3144
3145 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
3146 $response->header( 'Cache-Control: private, must-revalidate, max-age=0' );
3147 }
3148 if ( $this->mLastModified ) {
3149 $response->header( "Last-Modified: {$this->mLastModified}" );
3150 }
3151 } else {
3152 wfDebug( __METHOD__ . ': no caching **', 'private' );
3153
3154 # In general, the absence of a last modified header should be enough to prevent
3155 # the client from using its cache. We send a few other things just to make sure.
3156 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
3157 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
3158 }
3159 }
3160
3166 public function loadSkinModules( $sk ) {
3167 foreach ( $sk->getDefaultModules() as $group => $modules ) {
3168 if ( $group === 'styles' ) {
3169 foreach ( $modules as $moduleMembers ) {
3170 $this->addModuleStyles( $moduleMembers );
3171 }
3172 } else {
3173 $this->addModules( $modules );
3174 }
3175 }
3176 }
3177
3185 public function output( $return = false ) {
3186 if ( $this->mDoNothing ) {
3187 return $return ? '' : null;
3188 }
3189
3190 $request = $this->getRequest();
3191 $response = $request->response();
3192 $config = $this->getConfig();
3193
3194 if ( $this->mRedirect != '' ) {
3195 $services = MediaWikiServices::getInstance();
3196 // Modern standards don't require redirect URLs to be absolute, but make it so just in case.
3197 // Note that this doesn't actually guarantee an absolute URL: relative-path URLs are left intact.
3198 $this->mRedirect = (string)$services->getUrlUtils()->expand( $this->mRedirect, PROTO_CURRENT );
3199
3200 $redirect = $this->mRedirect;
3201 $code = $this->mRedirectCode;
3202 $content = '';
3203
3204 if ( $this->getHookRunner()->onBeforePageRedirect( $this, $redirect, $code ) ) {
3205 if ( $code == '301' || $code == '303' ) {
3206 if ( !$config->get( MainConfigNames::DebugRedirects ) ) {
3207 $response->statusHeader( (int)$code );
3208 }
3209 $this->mLastModified = wfTimestamp( TS::RFC2822 );
3210 }
3211 if ( $config->get( MainConfigNames::VaryOnXFP ) ) {
3212 $this->addVaryHeader( 'X-Forwarded-Proto' );
3213 }
3214 $this->sendCacheControl();
3215
3216 $response->header( 'Content-Type: text/html; charset=UTF-8' );
3217 if ( $config->get( MainConfigNames::DebugRedirects ) ) {
3218 $url = htmlspecialchars( $redirect );
3219 $content = "<!DOCTYPE html>\n<html>\n<head>\n"
3220 . "<title>Redirect</title>\n</head>\n<body>\n"
3221 . "<p>Location: <a href=\"$url\">$url</a></p>\n"
3222 . "</body>\n</html>\n";
3223
3224 if ( !$return ) {
3225 print $content;
3226 }
3227
3228 } else {
3229 $response->header( 'Location: ' . $redirect );
3230 }
3231 }
3232
3233 return $return ? $content : null;
3234 } elseif ( $this->mStatusCode ) {
3235 $response->statusHeader( $this->mStatusCode );
3236 }
3237
3238 # Buffer output; final headers may depend on later processing
3239 ob_start();
3240
3241 $response->header( 'Content-language: ' .
3242 MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
3243
3244 $linkHeader = $this->getLinkHeader();
3245 if ( $linkHeader ) {
3246 $response->header( $linkHeader );
3247 }
3248
3249 // Prevent framing, if requested
3250 $frameOptions = $this->getFrameOptions();
3251 if ( $frameOptions ) {
3252 $response->header( "X-Frame-Options: $frameOptions" );
3253 }
3254
3255 // Get the Origin-Trial header values. This is used to enable Chrome Origin
3256 // Trials: https://github.com/GoogleChrome/OriginTrials
3257 $originTrials = $config->get( MainConfigNames::OriginTrials );
3258 foreach ( $originTrials as $originTrial ) {
3259 $response->header( "Origin-Trial: $originTrial", false );
3260 }
3261
3262 $reportTo = $this->getReportTo();
3263 if ( $reportTo ) {
3264 $response->header( "Report-To: $reportTo" );
3265 }
3266
3267 $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
3268 if ( $featurePolicyReportOnly ) {
3269 $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
3270 }
3271
3272 if ( $this->mArticleBodyOnly ) {
3273 $response->header( 'Content-type: ' . $config->get( MainConfigNames::MimeType ) . '; charset=UTF-8' );
3274 if ( $this->cspOutputMode === self::CSP_HEADERS ) {
3275 $this->CSP->sendHeaders();
3276 }
3277 echo $this->mBodytext;
3278 } else {
3279 // Enable safe mode if requested (T152169)
3280 if ( $this->getRequest()->getBool( 'safemode' ) ) {
3281 $this->disallowUserJs();
3282 }
3283
3284 $sk = $this->getSkin();
3285 $skinOptions = $sk->getOptions();
3286
3287 if ( $skinOptions['format'] === 'json' ) {
3288 $response->header( 'Content-type: application/json; charset=UTF-8' );
3289 return json_encode( [
3290 '@WARNING' => $this->msg( 'skin-json-warning-message' )->escaped()
3291 ] + $sk->getTemplateData() );
3292 }
3293 $response->header( 'Content-type: ' . $config->get( MainConfigNames::MimeType ) . '; charset=UTF-8' );
3294 $this->loadSkinModules( $sk );
3295
3296 MWDebug::addModules( $this );
3297
3298 // Hook that allows last minute changes to the output page, e.g.
3299 // adding of CSS or JavaScript by extensions, adding CSP sources.
3300 $this->getHookRunner()->onBeforePageDisplay( $this, $sk );
3301
3302 if ( $this->cspOutputMode === self::CSP_HEADERS ) {
3303 $this->CSP->sendHeaders();
3304 }
3305
3306 try {
3307 $sk->outputPageFinal( $this );
3308 } catch ( Exception $e ) {
3309 ob_end_clean(); // bug T129657
3310 throw $e;
3311 }
3312 }
3313
3314 try {
3315 // This hook allows last minute changes to final overall output by modifying output buffer
3316 $this->getHookRunner()->onAfterFinalPageOutput( $this );
3317 } catch ( Exception $e ) {
3318 ob_end_clean(); // bug T129657
3319 throw $e;
3320 }
3321
3322 $this->sendCacheControl();
3323
3324 if ( $return ) {
3325 return ob_get_clean();
3326 } else {
3327 ob_end_flush();
3328 return null;
3329 }
3330 }
3331
3338 public function prepareErrorPage() {
3339 $this->setRobotPolicy( 'noindex,nofollow' );
3340 $this->setArticleRelated( false );
3341 $this->disableClientCache();
3342 $this->mRedirect = '';
3343 $this->clearSubtitle();
3344 $this->clearHTML();
3345 }
3346
3363 public function showErrorPage(
3364 $title, $msg, $params = [], $returnto = null, $returntoquery = null
3365 ) {
3366 if ( !$title instanceof Message ) {
3367 $title = $this->msg( $title );
3368 }
3369
3370 $this->prepareErrorPage();
3371 $this->setPageTitleMsg( $title );
3372
3373 if ( $msg instanceof Message ) {
3374 if ( $params !== [] ) {
3375 trigger_error( 'Argument ignored: $params. The message parameters argument '
3376 . 'is discarded when the $msg argument is a Message object instead of '
3377 . 'a string.', E_USER_NOTICE );
3378 }
3379 $this->addHTML( $msg->parseAsBlock() );
3380 } else {
3381 $this->addWikiMsgArray( $msg, $params );
3382 }
3383
3384 $this->addJsConfigVars( 'wgErrorPageMessageKey', is_string( $msg ) ? $msg : $msg->getKey() );
3385
3386 $this->returnToMain( null, $returnto, $returntoquery );
3387 }
3388
3395 public function showPermissionStatus( PermissionStatus $status, $action = null ) {
3396 Assert::precondition( !$status->isGood(), 'Status must have errors' );
3397
3398 $messages = $status->getMessages();
3399
3400 $services = MediaWikiServices::getInstance();
3401 $groupPermissionsLookup = $services->getGroupPermissionsLookup();
3402
3403 // For some actions (read, edit, create and upload), display a "login to do this action"
3404 // error if all of the following conditions are met:
3405 // 1. the user is not logged in as a named user, and so cannot be added to groups
3406 // 2. the only error is insufficient permissions (i.e. no block or something else)
3407 // 3. the error can be avoided simply by logging in
3408
3409 if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
3410 && !$this->getUser()->isNamed() && count( $messages ) == 1
3411 && ( $messages[0]->getKey() == 'badaccess-groups' || $messages[0]->getKey() == 'badaccess-group0' )
3412 && ( $groupPermissionsLookup->groupHasPermission( 'user', $action )
3413 || $groupPermissionsLookup->groupHasPermission( 'autoconfirmed', $action ) )
3414 ) {
3415 $displayReturnto = null;
3416
3417 # Due to T34276, if a user does not have read permissions,
3418 # $this->getTitle() will just give Special:Badtitle, which is
3419 # not especially useful as a returnto parameter. Use the title
3420 # from the request instead, if there was one.
3421 $request = $this->getRequest();
3422 $returnto = Title::newFromText( $request->getText( 'title' ) );
3423 if ( $action == 'edit' ) {
3424 $msg = 'whitelistedittext';
3425 $displayReturnto = $returnto;
3426 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
3427 $msg = 'nocreatetext';
3428 } elseif ( $action == 'upload' ) {
3429 $msg = 'uploadnologintext';
3430 } else {
3431 # Read
3432 $msg = 'loginreqpagetext';
3433 $displayReturnto = Title::newMainPage();
3434 }
3435
3436 $query = [];
3437
3438 if ( $returnto ) {
3439 $query['returnto'] = $returnto->getPrefixedText();
3440
3441 if ( !$request->wasPosted() ) {
3442 $returntoquery = $request->getQueryValues();
3443 unset( $returntoquery['title'] );
3444 unset( $returntoquery['returnto'] );
3445 unset( $returntoquery['returntoquery'] );
3446 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
3447 }
3448 }
3449
3450 $title = SpecialPage::getTitleFor( 'Userlogin' );
3451 $linkRenderer = $services->getLinkRenderer();
3452 $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
3453 $loginLink = $linkRenderer->makeKnownLink(
3454 $title,
3455 $this->msg( 'loginreqlink' )->text(),
3456 [],
3457 $query
3458 );
3459
3460 $this->prepareErrorPage();
3461 $this->setPageTitleMsg( $this->msg( 'loginreqtitle' ) );
3462 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
3463
3464 # Don't return to a page the user can't read otherwise
3465 # we'll end up in a pointless loop
3466 if ( $displayReturnto && $this->getAuthority()->probablyCan( 'read', $displayReturnto ) ) {
3467 $this->returnToMain( null, $displayReturnto );
3468 }
3469 } else {
3470 $this->prepareErrorPage();
3471 $this->setPageTitleMsg( $this->msg( 'permissionserrors' ) );
3472 $this->addWikiTextAsInterface( $this->formatPermissionStatus( $status, $action ) );
3473 }
3474 }
3475
3482 public function versionRequired( $version ) {
3483 $this->prepareErrorPage();
3484 $this->setPageTitleMsg(
3485 $this->msg( 'versionrequired' )->plaintextParams( $version )
3486 );
3487
3488 $this->addWikiMsg( 'versionrequiredtext', $version );
3489 $this->returnToMain();
3490 }
3491
3503 public function formatPermissionStatus( PermissionStatus $status, ?string $action = null ): string {
3504 if ( $status->isGood() ) {
3505 return '';
3506 }
3507
3508 $messages = array_map( fn ( $msg ) => $this->msg( $msg ), $status->getMessages() );
3509
3510 if ( $action == null ) {
3511 $text = $this->msg( 'permissionserrorstext', count( $messages ) )->plain() . "\n\n";
3512 } else {
3513 $action_desc = $this->msg( "action-$action" )->plain();
3514 $text = $this->msg(
3515 'permissionserrorstext-withaction',
3516 count( $messages ),
3517 $action_desc
3518 )->plain() . "\n\n";
3519 }
3520
3521 if ( count( $messages ) > 1 ) {
3522 $text .= Html::openElement( 'ul', [ 'class' => 'permissions-errors' ] );
3523 foreach ( $messages as $message ) {
3524 $text .= Html::rawElement(
3525 'li',
3526 [ 'class' => 'mw-permissionerror-' . $message->getKey() ],
3527 $message->plain()
3528 );
3529 }
3530 $text .= Html::closeElement( 'ul' );
3531 } else {
3532 $text .= Html::openElement( 'div', [ 'class' => 'permissions-errors' ] );
3533 $text .= Html::rawElement(
3534 'div',
3535 [ 'class' => 'mw-permissionerror-' . $messages[ 0 ]->getKey() ],
3536 $messages[ 0 ]->plain()
3537 );
3538 $text .= Html::closeElement( 'div' );
3539 }
3540
3541 return $text;
3542 }
3543
3553 public function showLagWarning( $lag ) {
3554 $config = $this->getConfig();
3555 if ( $lag >= $config->get( MainConfigNames::DatabaseReplicaLagWarning ) ) {
3556 // floor to avoid nano seconds to display
3557 $lag = floor( $lag );
3558 $message = $lag < $config->get( MainConfigNames::DatabaseReplicaLagCritical )
3559 ? 'lag-warn-normal'
3560 : 'lag-warn-high';
3561 // For grep: mw-lag-warn-normal, mw-lag-warn-high
3562 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
3563 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
3564 }
3565 }
3566
3575 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
3576 $linkRenderer = MediaWikiServices::getInstance()
3577 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
3578 $link = $this->msg( 'returnto' )->rawParams(
3579 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
3580 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
3581 }
3582
3591 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
3592 $returnto ??= $this->getRequest()->getText( 'returnto' );
3593
3594 $returntoquery ??= $this->getRequest()->getText( 'returntoquery' );
3595
3596 if ( $returnto === '' ) {
3597 $returnto = Title::newMainPage();
3598 }
3599
3600 if ( is_object( $returnto ) ) {
3601 $linkTarget = TitleValue::castPageToLinkTarget( $returnto );
3602 } else {
3603 $linkTarget = Title::newFromText( $returnto );
3604 }
3605
3606 // We don't want people to return to external interwiki. That
3607 // might potentially be used as part of a phishing scheme
3608 if ( !$linkTarget || $linkTarget->isExternal() ) {
3609 $linkTarget = Title::newMainPage();
3610 }
3611
3612 $this->addReturnTo( $linkTarget, wfCgiToArray( $returntoquery ) );
3613 }
3614
3633 public function showPendingTakeover(
3634 $fallbackUrl, $msg, ...$params
3635 ) {
3636 if ( $msg instanceof Message ) {
3637 if ( $params !== [] ) {
3638 trigger_error( 'Argument ignored: $params. The message parameters argument '
3639 . 'is discarded when the $msg argument is a Message object instead of '
3640 . 'a string.', E_USER_NOTICE );
3641 }
3642 $this->addHTML( $msg->parseAsBlock() );
3643 } else {
3644 $this->addHTML( $this->msg( $msg, ...$params )->parseAsBlock() );
3645 }
3646
3647 // Redirect if the user has no JS (<noscript>)
3648 $escapedUrl = htmlspecialchars( $fallbackUrl );
3649 $this->addHeadItem(
3650 'mw-noscript-fallback',
3651 // https://html.spec.whatwg.org/#attr-meta-http-equiv-refresh
3652 // means that if $fallbackUrl contains unencoded quotation marks
3653 // then this will behave confusingly, but shouldn't break the page
3654 "<noscript><meta http-equiv=\"refresh\" content=\"0; url=$escapedUrl\"></noscript>"
3655 );
3656 // Redirect if the user has no ResourceLoader
3657 $this->addScript( Html::inlineScript(
3658 '(window.NORLQ=window.NORLQ||[]).push(' .
3659 'function(){' .
3660 'location.href=' . json_encode( $fallbackUrl ) . ';' .
3661 '}' .
3662 ');'
3663 ) );
3664 }
3665
3676 private function inDebugMode() {
3677 if ( $this->debugMode === null ) {
3678 $resourceLoaderDebug = $this->getConfig()->get(
3679 MainConfigNames::ResourceLoaderDebug );
3680 $str = $this->getRequest()->getRawVal( 'debug' ) ??
3681 $this->getRequest()->getCookie( 'resourceLoaderDebug', '', $resourceLoaderDebug ? 'true' : '' );
3682 $this->debugMode = RL\Context::debugFromString( $str );
3683 }
3684 return $this->debugMode;
3685 }
3686
3687 private function getRlClientContext(): RL\Context {
3688 if ( !$this->rlClientContext ) {
3689 $query = ResourceLoader::makeLoaderQuery(
3690 [], // modules; not relevant
3691 $this->getLanguage()->getCode(),
3692 $this->getSkin()->getSkinName(),
3693 $this->getUser()->isRegistered() ? $this->getUser()->getName() : null,
3694 null, // version; not relevant
3695 $this->inDebugMode(),
3696 null, // only; not relevant
3697 $this->isPrintable()
3698 );
3699 $this->rlClientContext = new RL\Context(
3700 $this->getResourceLoader(),
3701 new FauxRequest( $query )
3702 );
3703 if ( $this->contentOverrideCallbacks ) {
3704 $this->rlClientContext = new RL\DerivativeContext( $this->rlClientContext );
3705 $this->rlClientContext->setContentOverrideCallback( function ( $page ) {
3706 foreach ( $this->contentOverrideCallbacks as $callback ) {
3707 $content = $callback( $page );
3708 if ( $content !== null ) {
3709 $text = ( $content instanceof TextContent ) ? $content->getText() : '';
3710 if ( preg_match( '/<\/?script/i', $text ) ) {
3711 // Proactively replace this so that we can display a message
3712 // to the user, instead of letting it go to Html::inlineScript(),
3713 // where it would be considered a server-side issue.
3714 $content = new JavaScriptContent(
3715 Html::encodeJsCall( 'mw.log.error', [
3716 "Cannot preview $page due to suspecting script tag inside (T200506)."
3717 ] )
3718 );
3719 }
3720 return $content;
3721 }
3722 }
3723 return null;
3724 } );
3725 }
3726 }
3727 return $this->rlClientContext;
3728 }
3729
3741 public function getRlClient() {
3742 if ( !$this->rlClient ) {
3743 $context = $this->getRlClientContext();
3744 $rl = $this->getResourceLoader();
3745 $this->addModules( [
3746 'user',
3747 'user.options',
3748 ] );
3749 $this->addModuleStyles( [
3750 'site.styles',
3751 'noscript',
3752 'user.styles',
3753 ] );
3754
3755 // Prepare exempt modules for buildExemptModules()
3756 $exemptGroups = [
3757 RL\Module::GROUP_SITE => [],
3758 RL\Module::GROUP_NOSCRIPT => [],
3759 RL\Module::GROUP_PRIVATE => [],
3760 RL\Module::GROUP_USER => []
3761 ];
3762 $exemptStates = [];
3763 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
3764
3765 // Preload getTitleInfo for isKnownEmpty calls below and in RL\ClientHtml
3766 // Separate user-specific batch for an improved cache-hit ratio.
3767 $userBatch = [ 'user.styles', 'user' ];
3768 $siteBatch = array_diff( $moduleStyles, $userBatch );
3769 RL\WikiModule::preloadTitleInfo( $context, $siteBatch );
3770 RL\WikiModule::preloadTitleInfo( $context, $userBatch );
3771
3772 // Filter out modules handled by buildExemptModules()
3773 $moduleStyles = array_filter( $moduleStyles,
3774 static function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3775 $module = $rl->getModule( $name );
3776 if ( $module ) {
3777 $group = $module->getGroup();
3778 if ( $group !== null && isset( $exemptGroups[$group] ) ) {
3779 // The `noscript` module is excluded from the client
3780 // side registry, no need to set its state either.
3781 // But we still output it. See T291735
3782 if ( $group !== RL\Module::GROUP_NOSCRIPT ) {
3783 $exemptStates[$name] = 'ready';
3784 }
3785 if ( !$module->isKnownEmpty( $context ) ) {
3786 // E.g. Don't output empty <styles>
3787 $exemptGroups[$group][] = $name;
3788 }
3789 return false;
3790 }
3791 }
3792 return true;
3793 }
3794 );
3795 $this->rlExemptStyleModules = $exemptGroups;
3796
3797 $config = $this->getConfig();
3798 // Client preferences are controlled by the skin and specific to unregistered
3799 // users. See mw.user.clientPrefs for details on how this works and how to
3800 // handle registered users.
3801 $clientPrefEnabled = (
3802 $this->getSkin()->getOptions()['clientPrefEnabled'] &&
3803 !$this->getUser()->isNamed()
3804 );
3805 $clientPrefCookiePrefix = $config->get( MainConfigNames::CookiePrefix );
3806
3807 $rlClient = new RL\ClientHtml( $context, [
3808 'target' => $this->getTarget(),
3809 // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3810 // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3811 // modules enqueued for loading on this page is filtered to just those.
3812 // However, to make sure we also apply the restriction to dynamic dependencies and
3813 // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3814 // StartupModule so that the client-side registry will not contain any restricted
3815 // modules either. (T152169, T185303)
3816 'safemode' => ( $this->getAllowedModules( RL\Module::TYPE_COMBINED )
3817 <= RL\Module::ORIGIN_CORE_INDIVIDUAL
3818 ) ? '1' : null,
3819 'clientPrefEnabled' => $clientPrefEnabled,
3820 'clientPrefCookiePrefix' => $clientPrefCookiePrefix,
3821 ] );
3822 $rlClient->setConfig( $this->getJSVars( self::JS_VAR_EARLY ) );
3823 $rlClient->setModules( $this->getModules( /*filter*/ true ) );
3824 $rlClient->setModuleStyles( $moduleStyles );
3825 $rlClient->setExemptStates( $exemptStates );
3826 $this->rlClient = $rlClient;
3827 }
3828 return $this->rlClient;
3829 }
3830
3836 public function headElement( Skin $sk, $includeStyle = true ) {
3837 $config = $this->getConfig();
3838 $userdir = $this->getLanguage()->getDir();
3839 $services = MediaWikiServices::getInstance();
3840 $sitedir = $services->getContentLanguage()->getDir();
3841
3842 $rlHtmlAtribs = $this->getRlClient()->getDocumentAttributes();
3843 $skinHtmlAttribs = $sk->getHtmlElementAttributes();
3844 // Combine the classes from different sources, and convert to a string, which is needed below
3845 $htmlClass = Html::expandClassList( [
3846 Html::expandClassList( $rlHtmlAtribs['class'] ?? [] ),
3847 Html::expandClassList( $skinHtmlAttribs['class'] ?? [] ),
3848 Html::expandClassList( $this->mAdditionalHtmlClasses )
3849 ] );
3850 if ( $htmlClass === '' ) {
3851 $htmlClass = null;
3852 }
3853 $htmlAttribs = array_merge( $rlHtmlAtribs, $skinHtmlAttribs, [ 'class' => $htmlClass ] );
3854
3855 $pieces = [];
3856 $pieces[] = Html::htmlHeader( $htmlAttribs );
3857 $pieces[] = Html::openElement( 'head' );
3858
3859 if ( $this->getHTMLTitle() == '' ) {
3860 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3861 }
3862
3863 if ( !Html::isXmlMimeType( $config->get( MainConfigNames::MimeType ) ) ) {
3864 // Add <meta charset="UTF-8">
3865 // This should be before <title> since it defines the charset used by
3866 // text including the text inside <title>.
3867 // The spec recommends defining XHTML5's charset using the XML declaration
3868 // instead of meta.
3869 // Our XML declaration is output by Html::htmlHeader.
3870 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3871 // https://html.spec.whatwg.org/multipage/semantics.html#charset
3872 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3873 }
3874
3875 $pieces[] = Html::element( 'title', [], $this->getHTMLTitle() );
3876 $pieces[] = $this->getRlClient()->getHeadHtml( $htmlClass );
3877 $pieces[] = $this->buildExemptModules();
3878 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3879 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3880
3881 $pieces[] = Html::closeElement( 'head' );
3882
3883 $skinOptions = $sk->getOptions();
3884 $bodyClasses = array_merge( $this->mAdditionalBodyClasses, $skinOptions['bodyClasses'] );
3885 $bodyClasses[] = 'mediawiki';
3886
3887 # Classes for LTR/RTL directionality support
3888 $bodyClasses[] = $userdir;
3889 $bodyClasses[] = "sitedir-$sitedir";
3890
3891 // See Article:showDiffPage for class to support article diff styling
3892
3893 $underline = $services->getUserOptionsLookup()->getOption( $this->getUser(), 'underline' );
3894 if ( $underline < 2 ) {
3895 // The following classes can be used here:
3896 // * mw-underline-always
3897 // * mw-underline-never
3898 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3899 }
3900
3901 // Parser feature migration class
3902 // The idea is that this will eventually be removed, after the wikitext
3903 // which requires it is cleaned up.
3904 $bodyClasses[] = 'mw-hide-empty-elt';
3905
3906 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3907 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3908 $bodyClasses[] =
3909 'action-' . Sanitizer::escapeClass( $this->getContext()->getActionName() );
3910
3911 if ( $sk->isResponsive() ) {
3912 $bodyClasses[] = 'skin--responsive';
3913 }
3914
3915 $bodyAttrs = [];
3916 // While the expandClassList() is not strictly needed, it's used for backwards compatibility
3917 // (this used to be built as a string and hooks likely still expect that).
3918 $bodyAttrs['class'] = Html::expandClassList( $bodyClasses );
3919
3920 $this->getHookRunner()->onOutputPageBodyAttributes( $this, $sk, $bodyAttrs );
3921
3922 $pieces[] = Html::openElement( 'body', $bodyAttrs );
3923
3924 // Add dedicated ARIA live region container for notifications to assistive technology users.
3925 // Note that `aria-atomic="false"` and `aria-relevant="additions text"` are the default
3926 // values and therefore not duplicated below.
3927 $pieces[] = Html::rawElement( 'div', [
3928 'id' => 'mw-aria-live-region',
3929 'class' => 'mw-aria-live-region',
3930 'aria-live' => 'polite',
3931 ], '' );
3932
3933 return self::combineWrappedStrings( $pieces );
3934 }
3935
3941 public function getResourceLoader() {
3942 if ( $this->mResourceLoader === null ) {
3943 // Lazy-initialise as needed
3944 $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3945 }
3946 return $this->mResourceLoader;
3947 }
3948
3957 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3958 // Apply 'origin' filters
3959 $modules = $this->filterModules( (array)$modules, null, $only );
3960
3961 return RL\ClientHtml::makeLoad(
3962 $this->getRlClientContext(),
3963 $modules,
3964 $only,
3965 $extraQuery
3966 );
3967 }
3968
3975 protected static function combineWrappedStrings( array $chunks ) {
3976 // Filter out empty values
3977 $chunks = array_filter( $chunks, 'strlen' );
3978 return WrappedString::join( "\n", $chunks );
3979 }
3980
3987 public function getBottomScripts() {
3988 // Keep the hook appendage separate to preserve WrappedString objects.
3989 // This enables to merge them where possible.
3990 $extraHtml = '';
3991 $this->getHookRunner()->onSkinAfterBottomScripts( $this->getSkin(), $extraHtml );
3992
3993 $chunks = [];
3994 $chunks[] = $this->getRlClient()->getBodyHtml();
3995
3996 // Legacy non-ResourceLoader scripts
3997 $chunks[] = $this->mScripts;
3998
3999 // Keep hostname and backend time as the first variables for quick view-source access.
4000 // These other variables will form a very long inline blob.
4001 $vars = [];
4002 if ( $this->getConfig()->get( MainConfigNames::ShowHostnames ) ) {
4003 $vars['wgHostname'] = wfHostname();
4004 }
4005 $elapsed = $this->getRequest()->getElapsedTime();
4006 // seconds to milliseconds
4007 $vars['wgBackendResponseTime'] = round( $elapsed * 1000 );
4008
4009 $vars += $this->getJSVars( self::JS_VAR_LATE );
4010 if ( $this->limitReportJSData ) {
4011 $vars['wgPageParseReport'] = $this->limitReportJSData;
4012 }
4013
4014 $rlContext = $this->getRlClientContext();
4015 $chunks[] = ResourceLoader::makeInlineScript(
4016 'mw.config.set(' . $rlContext->encodeJson( $vars ) . ');'
4017 );
4018
4019 $chunks = [ self::combineWrappedStrings( $chunks ) ];
4020 if ( $extraHtml !== '' ) {
4021 $chunks[] = $extraHtml;
4022 }
4023
4024 return WrappedString::join( "\n", $chunks );
4025 }
4026
4033 public function getJsConfigVars() {
4034 return $this->mJsConfigVars;
4035 }
4036
4043 public function addJsConfigVars( $keys, $value = null ) {
4044 if ( is_array( $keys ) ) {
4045 foreach ( $keys as $key => $value ) {
4046 $this->mJsConfigVars[$key] = $value;
4047 }
4048 return;
4049 }
4050
4051 $this->mJsConfigVars[$keys] = $value;
4052 }
4053
4072 public function getJSVars( ?int $flag = null ) {
4073 $curRevisionId = 0;
4074 $articleId = 0;
4075 // T23115
4076 $canonicalSpecialPageName = false;
4077 $services = MediaWikiServices::getInstance();
4078
4079 $title = $this->getTitle();
4080 $ns = $title->getNamespace();
4081 $nsInfo = $services->getNamespaceInfo();
4082 $canonicalNamespace = $nsInfo->exists( $ns )
4083 ? $nsInfo->getCanonicalName( $ns )
4084 : $title->getNsText();
4085
4086 $sk = $this->getSkin();
4087 // Get the relevant title so that AJAX features can use the correct page name
4088 // when making API requests from certain special pages (T36972).
4089 $relevantTitle = $sk->getRelevantTitle();
4090
4091 if ( $ns === NS_SPECIAL ) {
4092 [ $canonicalSpecialPageName, ] =
4093 $services->getSpecialPageFactory()->
4094 resolveAlias( $title->getDBkey() );
4095 } elseif ( $this->canUseWikiPage() ) {
4096 $wikiPage = $this->getWikiPage();
4097 // If we already know that the latest revision ID is the same as the revision ID being viewed,
4098 // avoid fetching it again, as it may give inconsistent results (T339164).
4099 if ( $this->isRevisionCurrent() && $this->getRevisionId() ) {
4100 $curRevisionId = $this->getRevisionId();
4101 } else {
4102 $curRevisionId = $wikiPage->getLatest();
4103 }
4104 $articleId = $wikiPage->getId();
4105 }
4106
4107 // ParserOutput informs HTML/CSS via lang/dir attributes.
4108 // We inform JavaScript via mw.config from here.
4109 $lang = $this->getContentLangForJS();
4110
4111 // Pre-process information
4112 $separatorTransTable = $lang->separatorTransformTable();
4113 $separatorTransTable = $separatorTransTable ?: [];
4114 $compactSeparatorTransTable = [
4115 implode( "\t", array_keys( $separatorTransTable ) ),
4116 implode( "\t", $separatorTransTable ),
4117 ];
4118 $digitTransTable = $lang->digitTransformTable();
4119 $digitTransTable = $digitTransTable ?: [];
4120 $compactDigitTransTable = [
4121 implode( "\t", array_keys( $digitTransTable ) ),
4122 implode( "\t", $digitTransTable ),
4123 ];
4124
4125 $user = $this->getUser();
4126
4127 // Internal variables for MediaWiki core
4128 $vars = [
4129 // @internal For mediawiki.page.ready
4130 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
4131
4132 // @internal For jquery.tablesorter
4133 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
4134 'wgDigitTransformTable' => $compactDigitTransTable,
4135 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
4136 'wgMonthNames' => $lang->getMonthNamesArray(),
4137
4138 // @internal For debugging purposes
4139 'wgRequestId' => WebRequest::getRequestId(),
4140 ];
4141
4142 // Start of supported and stable config vars (for use by extensions/gadgets).
4143 $vars += [
4144 'wgCanonicalNamespace' => $canonicalNamespace,
4145 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
4146 'wgNamespaceNumber' => $title->getNamespace(),
4147 'wgPageName' => $title->getPrefixedDBkey(),
4148 'wgTitle' => $title->getText(),
4149 'wgCurRevisionId' => $curRevisionId,
4150 'wgRevisionId' => (int)$this->getRevisionId(),
4151 'wgArticleId' => $articleId,
4152 'wgIsArticle' => $this->isArticle(),
4153 'wgIsRedirect' => $title->isRedirect(),
4154 'wgAction' => $this->getContext()->getActionName(),
4155 'wgUserName' => $user->isAnon() ? null : $user->getName(),
4156 'wgUserGroups' => $services->getUserGroupManager()->getUserEffectiveGroups( $user ),
4157 'wgCategories' => $this->getCategories(),
4158 'wgPageViewLanguage' => $lang->getCode(),
4159 'wgPageContentLanguage' => $lang->getCode(),
4160 'wgPageContentModel' => $title->getContentModel(),
4161 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
4162 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
4163 ];
4164 if ( $user->isRegistered() ) {
4165 $vars['wgUserId'] = $user->getId();
4166 $vars['wgUserIsTemp'] = $user->isTemp();
4167 $vars['wgUserEditCount'] = $user->getEditCount();
4168 $userReg = $user->getRegistration();
4169 $vars['wgUserRegistration'] = $userReg ? (int)wfTimestamp( TS::UNIX, $userReg ) * 1000 : null;
4170 $userFirstReg = $services->getUserRegistrationLookup()->getFirstRegistration( $user );
4171 $vars['wgUserFirstRegistration'] = $userFirstReg ?
4172 (int)wfTimestamp( TS::UNIX, $userFirstReg ) * 1000 : null;
4173 // Get the revision ID of the oldest new message on the user's talk
4174 // page. This can be used for constructing new message alerts on
4175 // the client side.
4176 $userNewMsgRevId = $this->getLastSeenUserTalkRevId();
4177 // Only occupy precious space in the <head> when it is non-null (T53640)
4178 // mw.config.get returns null by default.
4179 if ( $userNewMsgRevId ) {
4180 $vars['wgUserNewMsgRevisionId'] = $userNewMsgRevId;
4181 }
4182 } else {
4183 $tempUserCreator = $services->getTempUserCreator();
4184 if ( $tempUserCreator->isEnabled() ) {
4185 // For logged-out users only (without a temporary account): get the user name that will
4186 // be used for their temporary account, if it has already been acquired.
4187 // This may be used in previews.
4188 $session = $this->getRequest()->getSession();
4189 $vars['wgTempUserName'] = $tempUserCreator->getStashedName( $session );
4190 }
4191 }
4192 $languageConverter = $services->getLanguageConverterFactory()
4193 ->getLanguageConverter( $title->getPageLanguage() );
4194 if ( $languageConverter->hasVariants() ) {
4195 $vars['wgUserVariant'] = $languageConverter->getPreferredVariant();
4196 }
4197 // Same test as SkinTemplate
4198 $vars['wgIsProbablyEditable'] = $this->getAuthority()->probablyCan( 'edit', $title );
4199 $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle &&
4200 $this->getAuthority()->probablyCan( 'edit', $relevantTitle );
4201 $restrictionStore = $services->getRestrictionStore();
4202 foreach ( $restrictionStore->listApplicableRestrictionTypes( $title ) as $type ) {
4203 // Following keys are set in $vars:
4204 // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
4205 $vars['wgRestriction' . ucfirst( $type )] = $restrictionStore->getRestrictions( $title, $type );
4206 }
4207 if ( $title->isMainPage() ) {
4208 $vars['wgIsMainPage'] = true;
4209 }
4210
4211 $relevantUser = $sk->getRelevantUser();
4212 if ( $relevantUser ) {
4213 $vars['wgRelevantUserName'] = $relevantUser->getName();
4214 }
4215 // End of stable config vars
4216
4217 $titleFormatter = $services->getTitleFormatter();
4218
4219 if ( $this->mRedirectedFrom ) {
4220 // @internal For skin JS
4221 $vars['wgRedirectedFrom'] = $titleFormatter->getPrefixedDBkey( $this->mRedirectedFrom );
4222 }
4223
4224 // Allow extensions to add their custom variables to the mw.config map.
4225 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
4226 // page-dependent but site-wide (without state).
4227 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
4228 $this->getHookRunner()->onMakeGlobalVariablesScript( $vars, $this );
4229
4230 // Merge in variables from addJsConfigVars last
4231 $vars = array_merge( $vars, $this->getJsConfigVars() );
4232
4233 // Return only early or late vars if requested
4234 if ( $flag !== null ) {
4235 $lateVarNames =
4236 array_fill_keys( self::CORE_LATE_JS_CONFIG_VAR_NAMES, true ) +
4237 array_fill_keys( ExtensionRegistry::getInstance()->getAttribute( 'LateJSConfigVarNames' ), true );
4238 foreach ( $vars as $name => $_ ) {
4239 // If the variable's late flag doesn't match the requested late flag, unset it
4240 if ( isset( $lateVarNames[ $name ] ) !== ( $flag === self::JS_VAR_LATE ) ) {
4241 unset( $vars[ $name ] );
4242 }
4243 }
4244 }
4245
4246 return $vars;
4247 }
4248
4254 private function getLastSeenUserTalkRevId() {
4255 $services = MediaWikiServices::getInstance();
4256 $user = $this->getUser();
4257 $userHasNewMessages = $services
4258 ->getTalkPageNotificationManager()
4259 ->userHasNewMessages( $user );
4260 if ( !$userHasNewMessages ) {
4261 return null;
4262 }
4263
4264 $timestamp = $services
4265 ->getTalkPageNotificationManager()
4266 ->getLatestSeenMessageTimestamp( $user );
4267 if ( !$timestamp ) {
4268 return null;
4269 }
4270
4271 $revRecord = $services->getRevisionLookup()->getRevisionByTimestamp(
4272 $user->getTalkPage(),
4273 $timestamp
4274 );
4275 return $revRecord ? $revRecord->getId() : null;
4276 }
4277
4287 public function userCanPreview() {
4288 $request = $this->getRequest();
4289 if (
4290 $request->getRawVal( 'action' ) !== 'submit' ||
4291 !$request->wasPosted()
4292 ) {
4293 return false;
4294 }
4295
4296 $user = $this->getUser();
4297
4298 if ( !$user->isRegistered() ) {
4299 // Anons have predictable edit tokens
4300 return false;
4301 }
4302 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
4303 return false;
4304 }
4305
4306 $title = $this->getTitle();
4307 if ( !$this->getAuthority()->probablyCan( 'edit', $title ) ) {
4308 return false;
4309 }
4310
4311 return true;
4312 }
4313
4317 public function getHeadLinksArray() {
4318 $tags = [];
4319 $config = $this->getConfig();
4320
4321 if ( $this->cspOutputMode === self::CSP_META ) {
4322 foreach ( $this->CSP->getDirectives() as $header => $directive ) {
4323 $tags["meta-csp-$header"] = Html::element( 'meta', [
4324 'http-equiv' => $header,
4325 'content' => $directive,
4326 ] );
4327 }
4328 }
4329
4330 $tags['meta-generator'] = Html::element( 'meta', [
4331 'name' => 'generator',
4332 'content' => 'MediaWiki ' . MW_VERSION,
4333 ] );
4334
4335 if ( $config->get( MainConfigNames::ReferrerPolicy ) !== false ) {
4336 // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
4337 // fallbacks should come before the primary value so we need to reverse the array.
4338 foreach ( array_reverse( (array)$config->get( MainConfigNames::ReferrerPolicy ) ) as $i => $policy ) {
4339 $tags["meta-referrer-$i"] = Html::element( 'meta', [
4340 'name' => 'referrer',
4341 'content' => $policy,
4342 ] );
4343 }
4344 }
4345
4346 $p = $this->getRobotsContent();
4347 if ( $p ) {
4348 // http://www.robotstxt.org/wc/meta-user.html
4349 // Only show if it's different from the default robots policy
4350 $tags['meta-robots'] = Html::element( 'meta', [
4351 'name' => 'robots',
4352 'content' => $p,
4353 ] );
4354 }
4355
4356 # Browser based phone number detection
4357 if ( $config->get( MainConfigNames::BrowserFormatDetection ) !== false ) {
4358 $tags['meta-format-detection'] = Html::element( 'meta', [
4359 'name' => 'format-detection',
4360 'content' => $config->get( MainConfigNames::BrowserFormatDetection ),
4361 ] );
4362 }
4363
4364 foreach ( $this->mMetatags as [ $name, $val ] ) {
4365 $attrs = [];
4366 if ( strncasecmp( $name, 'http:', 5 ) === 0 ) {
4367 $name = substr( $name, 5 );
4368 $attrs['http-equiv'] = $name;
4369 } elseif ( strncasecmp( $name, 'og:', 3 ) === 0 ) {
4370 $attrs['property'] = $name;
4371 } else {
4372 $attrs['name'] = $name;
4373 }
4374 $attrs['content'] = $val;
4375 $tagName = "meta-$name";
4376 if ( isset( $tags[$tagName] ) ) {
4377 $tagName .= $val;
4378 }
4379 $tags[$tagName] = Html::element( 'meta', $attrs );
4380 }
4381
4382 foreach ( $this->mLinktags as $tag ) {
4383 $tags[] = Html::element( 'link', $tag );
4384 }
4385
4386 if ( $config->get( MainConfigNames::UniversalEditButton ) && $this->isArticleRelated() ) {
4387 if ( $this->getAuthority()->probablyCan( 'edit', $this->getTitle() ) ) {
4388 $msg = $this->msg( 'edit' )->text();
4389 // Use mime type per https://phabricator.wikimedia.org/T21165#6946526
4390 $tags['universal-edit-button'] = Html::element( 'link', [
4391 'rel' => 'alternate',
4392 'type' => 'application/x-wiki',
4393 'title' => $msg,
4394 'href' => $this->getTitle()->getEditURL(),
4395 ] );
4396 }
4397 }
4398
4399 # Generally, the order of the favicon and apple-touch-icon links
4400 # should not matter, but Konqueror (3.5.9 at least) incorrectly
4401 # uses whichever one appears later in the HTML source. Make sure
4402 # apple-touch-icon is specified first to avoid this.
4403 $appleTouchIconHref = $config->get( MainConfigNames::AppleTouchIcon );
4404 # Browser look for those by default, unnecessary to set a link tag
4405 if (
4406 $appleTouchIconHref !== false &&
4407 $appleTouchIconHref !== '/apple-touch-icon.png' &&
4408 $appleTouchIconHref !== '/apple-touch-icon-precomposed.png'
4409 ) {
4410 $tags['apple-touch-icon'] = Html::element( 'link', [
4411 'rel' => 'apple-touch-icon',
4412 'href' => $appleTouchIconHref
4413 ] );
4414 }
4415
4416 $faviconHref = $config->get( MainConfigNames::Favicon );
4417 # Browser look for those by default, unnecessary to set a link tag
4418 if ( $faviconHref !== false && $faviconHref !== '/favicon.ico' ) {
4419 $tags['favicon'] = Html::element( 'link', [
4420 'rel' => 'icon',
4421 'href' => $faviconHref
4422 ] );
4423 }
4424
4425 # OpenSearch description link
4426 $tags['opensearch'] = Html::element( 'link', [
4427 'rel' => 'search',
4428 'type' => 'application/opensearchdescription+xml',
4429 'href' => wfScript( 'rest' ) . '/v1/search',
4430 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
4431 ] );
4432
4433 $services = MediaWikiServices::getInstance();
4434
4435 # Real Simple Discovery link, provides auto-discovery information
4436 # for the MediaWiki API (and potentially additional custom API
4437 # support such as WordPress or Twitter-compatible APIs for a
4438 # blogging extension, etc)
4439 $tags['rsd'] = Html::element( 'link', [
4440 'rel' => 'EditURI',
4441 'type' => 'application/rsd+xml',
4442 // Output a protocol-relative URL here if $wgServer is protocol-relative.
4443 // Whether RSD accepts relative or protocol-relative URLs is completely
4444 // undocumented, though.
4445 'href' => (string)$services->getUrlUtils()->expand( wfAppendQuery(
4446 wfScript( 'api' ),
4447 [ 'action' => 'rsd' ] ),
4449 ),
4450 ] );
4451
4452 $tags = array_merge(
4453 $tags,
4454 $this->getHeadLinksCanonicalURLArray( $config ),
4455 $this->getHeadLinksAlternateURLsArray(),
4456 $this->getHeadLinksCopyrightArray( $config ),
4457 $this->getHeadLinksSyndicationArray( $config ),
4458 );
4459
4460 // Allow extensions to add, remove and/or otherwise manipulate these links
4461 // If you want only to *add* <head> links, please use the addHeadItem()
4462 // (or addHeadItems() for multiple items) method instead.
4463 // This hook is provided as a last resort for extensions to modify these
4464 // links before the output is sent to client.
4465 $this->getHookRunner()->onOutputPageAfterGetHeadLinksArray( $tags, $this );
4466
4467 return $tags;
4468 }
4469
4489 private function getHeadLinksCanonicalURLArray( Config $config ) {
4490 $tags = [];
4491 $canonicalUrl = $this->mCanonicalUrl;
4492
4493 if ( $config->get( MainConfigNames::EnableCanonicalServerLink ) ) {
4494 $query = [];
4495 $action = $this->getContext()->getActionName();
4496 $isCanonicalUrlAction = in_array( $action, [ 'history', 'info' ] );
4497 $services = MediaWikiServices::getInstance();
4498 $languageConverterFactory = $services->getLanguageConverterFactory();
4499 $isLangConversionDisabled = $languageConverterFactory->isConversionDisabled();
4500 $pageLang = $this->getTitle()->getPageLanguage();
4501 $pageLanguageConverter = $languageConverterFactory->getLanguageConverter( $pageLang );
4502 $urlVariant = $pageLanguageConverter->getURLVariant();
4503
4504 if ( $canonicalUrl !== false ) {
4505 $canonicalUrl = (string)$services->getUrlUtils()->expand( $canonicalUrl, PROTO_CANONICAL );
4506 } elseif ( $this->isArticleRelated() ) {
4507 if ( $isCanonicalUrlAction ) {
4508 $query['action'] = $action;
4509 } elseif ( !$isLangConversionDisabled && $urlVariant ) {
4510 # T54429, T108443: Making canonical URL language-variant-aware.
4511 $query['variant'] = $urlVariant;
4512 }
4513 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
4514 } else {
4515 $reqUrl = $this->getRequest()->getRequestURL();
4516 $canonicalUrl = (string)$services->getUrlUtils()->expand( $reqUrl, PROTO_CANONICAL );
4517 }
4518 }
4519
4520 if ( $canonicalUrl !== false ) {
4521 $tags['link-canonical'] = Html::element( 'link', [
4522 'rel' => 'canonical',
4523 'href' => $canonicalUrl
4524 ] );
4525 }
4526
4527 return $tags;
4528 }
4529
4538 private function getHeadLinksAlternateURLsArray() {
4539 $tags = [];
4540 $languageUrls = [];
4541 $action = $this->getContext()->getActionName();
4542 $isCanonicalUrlAction = in_array( $action, [ 'history', 'info' ] );
4543 $services = MediaWikiServices::getInstance();
4544 $languageConverterFactory = $services->getLanguageConverterFactory();
4545 $isLangConversionDisabled = $languageConverterFactory->isConversionDisabled();
4546 $pageLang = $this->getTitle()->getPageLanguage();
4547 $pageLanguageConverter = $languageConverterFactory->getLanguageConverter( $pageLang );
4548
4549 # Language variants
4550 if (
4551 $this->isArticleRelated() &&
4552 !$isCanonicalUrlAction &&
4553 $pageLanguageConverter->hasVariants() &&
4554 !$isLangConversionDisabled
4555 ) {
4556 $variants = $pageLanguageConverter->getVariants();
4557 foreach ( $variants as $variant ) {
4558 $bcp47 = LanguageCode::bcp47( $variant );
4559 $languageUrls[$bcp47] = $this->getTitle()
4560 ->getFullURL( [ 'variant' => $variant ], false, PROTO_CURRENT );
4561 }
4562 }
4563
4564 # Alternate URLs for interlanguage links would be handeled in HTML body tag instead of
4565 # head tag, see T326829.
4566
4567 if ( $languageUrls ) {
4568 # Force the alternate URL of page language code to be self.
4569 # T123901, T305540, T108443: Override mixed-variant variant link in language variant links.
4570 $currentUrl = $this->getTitle()->getFullURL( [], false, PROTO_CURRENT );
4571 $pageLangCodeBcp47 = LanguageCode::bcp47( $pageLang->getCode() );
4572 $languageUrls[$pageLangCodeBcp47] = $currentUrl;
4573
4574 ksort( $languageUrls );
4575
4576 # Also add x-default link per https://support.google.com/webmasters/answer/189077?hl=en
4577 $languageUrls['x-default'] = $currentUrl;
4578
4579 # Process all of language variants and interlanguage links
4580 foreach ( $languageUrls as $bcp47 => $languageUrl ) {
4581 $bcp47lowercase = strtolower( $bcp47 );
4582 $tags['link-alternate-language-' . $bcp47lowercase] = Html::element( 'link', [
4583 'rel' => 'alternate',
4584 'hreflang' => $bcp47,
4585 'href' => $languageUrl,
4586 ] );
4587 }
4588 }
4589
4590 return $tags;
4591 }
4592
4599 private function getHeadLinksCopyrightArray( Config $config ) {
4600 $tags = [];
4601
4602 if ( $this->copyrightUrl !== null ) {
4603 $copyright = $this->copyrightUrl;
4604 } else {
4605 $copyright = '';
4606 if ( $config->get( MainConfigNames::RightsPage ) ) {
4607 $copy = Title::newFromText( $config->get( MainConfigNames::RightsPage ) );
4608
4609 if ( $copy ) {
4610 $copyright = $copy->getLocalURL();
4611 }
4612 }
4613
4614 if ( !$copyright && $config->get( MainConfigNames::RightsUrl ) ) {
4615 $copyright = $config->get( MainConfigNames::RightsUrl );
4616 }
4617 }
4618
4619 if ( $copyright ) {
4620 $tags['copyright'] = Html::element( 'link', [
4621 'rel' => 'license',
4622 'href' => $copyright
4623 ] );
4624 }
4625
4626 return $tags;
4627 }
4628
4635 private function getHeadLinksSyndicationArray( Config $config ) {
4636 if ( !$config->get( MainConfigNames::Feed ) ) {
4637 return [];
4638 }
4639
4640 $tags = [];
4641 $feedLinks = [];
4642
4643 foreach ( $this->getSyndicationLinks() as $format => $link ) {
4644 # Use the page name for the title. In principle, this could
4645 # lead to issues with having the same name for different feeds
4646 # corresponding to the same page, but we can't avoid that at
4647 # this low a level.
4648
4649 $feedLinks[] = $this->feedLink(
4650 $format,
4651 $link,
4652 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
4653 $this->msg(
4654 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
4655 )->text()
4656 );
4657 }
4658
4659 # Recent changes feed should appear on every page (except recentchanges,
4660 # that would be redundant). Put it after the per-page feed to avoid
4661 # changing existing behavior. It's still available, probably via a
4662 # menu in your browser. Some sites might have a different feed they'd
4663 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
4664 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
4665 # If so, use it instead.
4666 $sitename = $config->get( MainConfigNames::Sitename );
4667 $overrideSiteFeed = $config->get( MainConfigNames::OverrideSiteFeed );
4668 if ( $overrideSiteFeed ) {
4669 foreach ( $overrideSiteFeed as $type => $feedUrl ) {
4670 // Note, this->feedLink escapes the url.
4671 $feedLinks[] = $this->feedLink(
4672 $type,
4673 $feedUrl,
4674 $this->msg( "site-{$type}-feed", $sitename )->text()
4675 );
4676 }
4677 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
4678 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
4679 foreach ( $this->getAdvertisedFeedTypes() as $format ) {
4680 $feedLinks[] = $this->feedLink(
4681 $format,
4682 $rctitle->getLocalURL( [ 'feed' => $format ] ),
4683 # For grep: 'site-rss-feed', 'site-atom-feed'
4684 $this->msg( "site-{$format}-feed", $sitename )->text()
4685 );
4686 }
4687 }
4688
4689 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
4690 # manipulating or removing existing feed tags. If you want to add new feeds, you should
4691 # use OutputPage::addFeedLink() instead.
4692 $this->getHookRunner()->onAfterBuildFeedLinks( $feedLinks );
4693
4694 $tags += $feedLinks;
4695
4696 return $tags;
4697 }
4698
4707 private function feedLink( $type, $url, $text ) {
4708 return Html::element( 'link', [
4709 'rel' => 'alternate',
4710 'type' => "application/$type+xml",
4711 'title' => $text,
4712 'href' => $url ]
4713 );
4714 }
4715
4725 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
4726 $options = [];
4727 if ( $media ) {
4728 $options['media'] = $media;
4729 }
4730 if ( $condition ) {
4731 $options['condition'] = $condition;
4732 }
4733 if ( $dir ) {
4734 $options['dir'] = $dir;
4735 }
4736 $this->styles[$style] = $options;
4737 }
4738
4747 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
4748 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
4749 # If wanted, and the interface is right-to-left, flip the CSS
4750 $style_css = CSSJanus::transform( $style_css, true, false );
4751 }
4752 $this->mInlineStyles .= Html::inlineStyle( $style_css );
4753 }
4754
4760 protected function buildExemptModules() {
4761 $chunks = [];
4762
4763 // Requirements:
4764 // - Within modules provided by the software (core, skin, extensions),
4765 // styles from skin stylesheets should be overridden by styles
4766 // from modules dynamically loaded with JavaScript.
4767 // - Styles from site-specific, private, and user modules should override
4768 // both of the above.
4769 //
4770 // The effective order for stylesheets must thus be:
4771 // 1. Page style modules, formatted server-side by RL\ClientHtml.
4772 // 2. Dynamically-loaded styles, inserted client-side by mw.loader.
4773 // 3. Styles that are site-specific, private or from the user, formatted
4774 // server-side by this function.
4775 //
4776 // The 'ResourceLoaderDynamicStyles' marker helps JavaScript know where
4777 // point #2 is.
4778
4779 // Add legacy styles added through addStyle()/addInlineStyle() here
4780 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
4781
4782 // Things that go after the ResourceLoaderDynamicStyles marker
4783 $append = [];
4784 $separateReq = [ 'site.styles', 'user.styles' ];
4785 foreach ( $this->rlExemptStyleModules as $moduleNames ) {
4786 if ( $moduleNames ) {
4787 $append[] = $this->makeResourceLoaderLink(
4788 array_diff( $moduleNames, $separateReq ),
4789 RL\Module::TYPE_STYLES
4790 );
4791
4792 foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
4793 // These require their own dedicated request in order to support "@import"
4794 // syntax, which is incompatible with concatenation. (T147667, T37562)
4795 $append[] = $this->makeResourceLoaderLink( $name,
4796 RL\Module::TYPE_STYLES
4797 );
4798 }
4799 }
4800 }
4801 if ( $append ) {
4802 $chunks[] = Html::element(
4803 'meta',
4804 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
4805 );
4806 $chunks = array_merge( $chunks, $append );
4807 }
4808
4809 return self::combineWrappedStrings( $chunks );
4810 }
4811
4815 public function buildCssLinksArray() {
4816 $links = [];
4817
4818 foreach ( $this->styles as $file => $options ) {
4819 $link = $this->styleLink( $file, $options );
4820 if ( $link ) {
4821 $links[$file] = $link;
4822 }
4823 }
4824 return $links;
4825 }
4826
4834 protected function styleLink( $style, array $options ) {
4835 if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
4836 return '';
4837 }
4838
4839 if ( isset( $options['media'] ) ) {
4840 $media = self::transformCssMedia( $options['media'] );
4841 if ( $media === null ) {
4842 return '';
4843 }
4844 } else {
4845 $media = 'all';
4846 }
4847
4848 if ( str_starts_with( $style, '/' ) ||
4849 str_starts_with( $style, 'http:' ) ||
4850 str_starts_with( $style, 'https:' )
4851 ) {
4852 $url = $style;
4853 } else {
4854 $config = $this->getConfig();
4855 // Append file hash as query parameter
4856 $url = self::transformResourcePath(
4857 $config,
4858 $config->get( MainConfigNames::StylePath ) . '/' . $style
4859 );
4860 }
4861
4862 $link = Html::linkedStyle( $url, $media );
4863
4864 if ( isset( $options['condition'] ) ) {
4865 $condition = htmlspecialchars( $options['condition'] );
4866 $link = "<!--[if $condition]>$link<![endif]-->";
4867 }
4868 return $link;
4869 }
4870
4892 public static function transformResourcePath( Config $config, $path ) {
4893 $localDir = MW_INSTALL_PATH;
4894 $remotePathPrefix = $config->get( MainConfigNames::ResourceBasePath );
4895 if ( $remotePathPrefix === '' ) {
4896 // The configured base path is required to be empty string for
4897 // wikis in the domain root
4898 $remotePath = '/';
4899 } else {
4900 $remotePath = $remotePathPrefix;
4901 }
4902 if ( !str_starts_with( $path, $remotePath ) || str_starts_with( $path, '//' ) ) {
4903 // - Path is outside wgResourceBasePath, ignore.
4904 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
4905 return $path;
4906 }
4907 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
4908 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
4909 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
4910 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
4911 $uploadPath = $config->get( MainConfigNames::UploadPath );
4912 if ( str_starts_with( $path, $uploadPath ) ) {
4913 $localDir = $config->get( MainConfigNames::UploadDirectory );
4914 $remotePathPrefix = $remotePath = $uploadPath;
4915 }
4916
4917 $path = RelPath::getRelativePath( $path, $remotePath );
4918 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
4919 }
4920
4932 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
4933 // This MUST match the equivalent logic in CSSMin::remapOne()
4934 $localFile = "$localPath/$file";
4935 $url = "$remotePathPrefix/$file";
4936 if ( is_file( $localFile ) ) {
4937 $hash = md5_file( $localFile );
4938 if ( $hash === false ) {
4939 wfLogWarning( __METHOD__ . ": Failed to hash $localFile" );
4940 $hash = '';
4941 }
4942 $url .= '?' . substr( $hash, 0, 5 );
4943 }
4944 return $url;
4945 }
4946
4954 public static function transformCssMedia( $media ) {
4955 global $wgRequest;
4956
4957 if ( $wgRequest->getBool( 'printable' ) ) {
4958 // When browsing with printable=yes, apply "print" media styles
4959 // as if they are screen styles (no media, media="").
4960 if ( $media === 'print' ) {
4961 return '';
4962 }
4963
4964 // https://www.w3.org/TR/css3-mediaqueries/#syntax
4965 //
4966 // This regex will not attempt to understand a comma-separated media_query_list
4967 // Example supported values for $media:
4968 //
4969 // 'screen', 'only screen', 'screen and (min-width: 982px)' ),
4970 //
4971 // Example NOT supported value for $media:
4972 //
4973 // '3d-glasses, screen, print and resolution > 90dpi'
4974 //
4975 // If it's a "printable" request, we disable all screen stylesheets.
4976 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
4977 if ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
4978 return null;
4979 }
4980 }
4981
4982 return $media;
4983 }
4984
4993 public function addWikiMsg( $name, ...$args ) {
4994 $this->addWikiMsgArray( $name, $args );
4995 }
4996
5006 public function addWikiMsgArray( $name, $args ) {
5007 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
5008 }
5009
5036 public function wrapWikiMsg( $wrap, ...$msgSpecs ) {
5037 $s = $wrap;
5038 foreach ( $msgSpecs as $n => $spec ) {
5039 if ( is_array( $spec ) ) {
5040 $args = $spec;
5041 $name = array_shift( $args );
5042 } else {
5043 $args = [];
5044 $name = $spec;
5045 }
5046 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
5047 }
5048
5049 $title = $this->getTitle();
5050 if ( $title === null ) {
5051 throw new RuntimeException( 'No title in ' . __METHOD__ );
5052 }
5053 $popts = $this->internalParserOptions( true );
5054 // We are *mostly* parsing a message. Other code wants to rely on that. (T395196)
5055 // It would be cleaner if the wrappers were added outside of wikitext parsing, so we could
5056 // really just parse the message, but it seems scary to change that now.
5057 $popts->setIsMessage( true );
5058 $this->addWikiTextTitleInternal( $s, $title, /*linestart*/ true, $popts );
5059 }
5060
5067 public function isTOCEnabled() {
5068 return $this->mEnableTOC;
5069 }
5070
5076 public function addTOCPlaceholder( TOCData $tocData ): void {
5077 $pout = new ParserOutput;
5078 $pout->setTOCData( $tocData );
5079 $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC );
5080 $pout->setRawText( Parser::TOC_PLACEHOLDER );
5081 $this->addParserOutput( $pout, $this->internalParserOptions( false ) );
5082 }
5083
5091 public static function setupOOUI( $skinName = null, $dir = null ) {
5092 if ( !self::$oouiSetupDone ) {
5093 self::$oouiSetupDone = true;
5094 $context = RequestContext::getMain();
5095 $skinName = $context->getSkinName();
5096 $dir = $context->getLanguage()->getDir();
5097 $themes = RL\OOUIFileModule::getSkinThemeMap();
5098 $theme = $themes[$skinName] ?? $themes['default'];
5099 // For example, 'OOUI\WikimediaUITheme'.
5100 $themeClass = "OOUI\\{$theme}Theme";
5101 Theme::setSingleton( new $themeClass() );
5102 Element::setDefaultDir( $dir );
5103 }
5104 }
5105
5111 public static function resetOOUI() {
5112 if ( self::$oouiSetupDone ) {
5113 self::$oouiSetupDone = false;
5114 self::setupOOUI();
5115 }
5116 }
5117
5124 public function enableOOUI() {
5125 self::setupOOUI();
5126 $this->addModuleStyles( [
5127 'oojs-ui-core.styles',
5128 'oojs-ui.styles.indicators',
5129 'mediawiki.widgets.styles',
5130 'oojs-ui-core.icons',
5131 ] );
5132 }
5133
5140 public function getCSP() {
5141 return $this->CSP;
5142 }
5143
5157 public function setCspOutputMode( string $mode ): void {
5158 $this->cspOutputMode = $mode;
5159 }
5160
5170 public function tailElement( $skin ) {
5171 $tail = [
5172 MWDebug::getDebugHTML( $skin ),
5173 $this->getBottomScripts(),
5174 MWDebug::getHTMLDebugLog(),
5175 Html::closeElement( 'body' ),
5176 Html::closeElement( 'html' ),
5177 ];
5178
5179 return WrappedStringList::join( "\n", $tail );
5180 }
5181}
5182
5184class_alias( OutputPage::class, 'OutputPage' );
const PROTO_CANONICAL
Definition Defines.php:223
const PROTO_CURRENT
Definition Defines.php:222
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:23
const NS_MEDIAWIKI
Definition Defines.php:59
const NS_SPECIAL
Definition Defines.php:40
const PROTO_RELATIVE
Definition Defines.php:219
const NS_CATEGORY
Definition Defines.php:65
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,...
wfHostname()
Get host name of the current machine, for use in error reporting.
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
global $wgRequest
Definition Setup.php:434
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
Content for JavaScript pages.
Content object implementation for representing flat text.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
setContext(IContextSource $context)
Group all the pieces relevant to the context of a request into one instance.
Debug toolbar.
Definition MWDebug.php:35
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:79
exists()
Returns true if file exists in the repository.
Definition File.php:1036
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
Methods for dealing with language codes.
Base class for language-specific code.
Definition Language.php:69
A class containing constants representing the names of configuration variables.
const UseCdn
Name constant for the UseCdn setting, for use with Config::get()
const CdnMaxAge
Name constant for the CdnMaxAge setting, for use with Config::get()
const CachePages
Name constant for the CachePages setting, for use with Config::get()
const CacheEpoch
Name constant for the CacheEpoch setting, for use with Config::get()
Service locator for MediaWiki core services.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
This is one of the Core classes and should be read at least once by any new developers.
getResourceLoader()
Get a ResourceLoader object associated with this OutputPage.
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
getCSP()
Get the ContentSecurityPolicy object.
getCacheVaryCookies()
Get the list of cookie names that will influence the cache.
showLagWarning( $lag)
Show a warning about replica DB lag.
addVaryHeader( $header)
Add an HTTP header that will have an influence on the cache.
getOutputFlag(ParserOutputFlags|string $name)
getUnprefixedDisplayTitle()
Returns page display title without the namespace prefix if possible.
setDisplayTitle( $html)
Same as page title but only contains the name of the page, not any other text.
addWikiMsg( $name,... $args)
Add a wikitext-formatted message to the output.
disableClientCache()
Force the page to send nocache headers.
getIndicators()
Get the indicators associated with this page.
setSubtitle( $str)
Replace the subtitle with $str.
string $mInlineStyles
Inline CSS styles.
setCopyrightUrl( $url)
Set the copyright URL to send with the output.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
setCspOutputMode(string $mode)
Sets the output mechanism for content security policies (HTTP headers or meta tags).
tailElement( $skin)
The final bits that go to the bottom of a page HTML document including the closing tags.
showErrorPage( $title, $msg, $params=[], $returnto=null, $returntoquery=null)
Output a standard error page.
getMetaTags()
Returns the current <meta> tags.
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
setLanguageLinks(array $newLinkArray)
Reset the language links and add new language links.
static resetOOUI()
Notify of a change in global skin or language which would necessitate reinitialization of OOUI global...
getLinkHeader()
Return a Link: header.
addHeadItems( $values)
Add one or more head items to the output.
static setupOOUI( $skinName=null, $dir=null)
Helper function to setup the PHP implementation of OOUI to use in this request.
getProperty( $name)
Get an additional output property.
addPostProcessedParserOutput(ParserOutput $parserOutput)
bool $mDoNothing
Whether output is disabled.
setPreventClickjacking(bool $enable)
Set the prevent-clickjacking flag.
addStyle( $style, $media='', $condition='', $dir='')
Add a local or specified stylesheet, with the given media options.
hasHeadItem( $name)
Check if the header item $name is already set.
getJSVars(?int $flag=null)
Get an array containing the variables to be set in mw.config in JavaScript.
formatPermissionStatus(PermissionStatus $status, ?string $action=null)
Format permission $status obtained from Authority for display.
parserOptions()
Get/set the ParserOptions object to use for wikitext parsing.
forceHideNewSectionLink()
Forcibly hide the new section link?
getLinkTags()
Returns the current <link> tags.
getLanguageLinks()
Get the list of language links.
disable()
Disable output completely, i.e.
showsCopyright()
Return whether the standard copyright should be shown for the current page.
getRlClient()
Call this to freeze the module queue and JS config and create a formatter.
filterModules(array $modules, $position=null, $type=RL\Module::TYPE_COMBINED)
Filter an array of modules to remove members not considered to be trustworthy, and modules which are ...
disallowUserJs()
Do not allow scripts which can be modified by wiki users to load on this page; only allow scripts bun...
makeResourceLoaderLink( $modules, $only, array $extraQuery=[])
Explicitly load or embed modules on a page.
setSyndicated( $show=true)
Add or remove feed links in the page header This is mainly kept for backward compatibility,...
getRevisionTimestamp()
Get the timestamp of displayed revision.
setRedirectedFrom(PageReference $t)
Set $mRedirectedFrom, the page which redirected us to the current page.
getModules( $filter=false,... $args)
Get the list of modules to include on this page.
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
getHTMLTitle()
Return the "HTML title", i.e.
addParserOutputContent(ParserOutput $parserOutput, $parserOptions=null, $poOptions=null)
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
getFeedAppendQuery()
Will currently always return null.
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
addBodyClasses( $classes)
Add a class to the <body> element.
lowerCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header to $maxage if that is lower t...
setTitle(PageReference $t)
Set the Title object to use.
setContentLangForJS(Bcp47Code $lang)
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
getAllowedModules( $type)
Show what level of JavaScript / CSS untrustworthiness is allowed on this page.
string $mBodytext
Contains all of the "<body>" content.
addParserOutput(ParserOutput $parserOutput, $parserOptions=null, $poOptions=null)
Add everything from a ParserOutput object.
headElement(Skin $sk, $includeStyle=true)
setIndicators(array $indicators)
Add an array of indicators, with their identifiers as array keys and HTML contents as values.
setFeedAppendQuery( $val)
Add default feeds to the page header This is mainly kept for backward compatibility,...
isArticle()
Return whether the content displayed page is related to the source of the corresponding article on th...
addLanguageLinks(array $newLinkArray)
Add new language links.
getNoGallery()
Get the "no gallery" flag.
setLastModified( $timestamp)
Override the last modified timestamp.
getFrameOptions()
Get the X-Frame-Options header value (without the name part), or false if there isn't one.
getJsConfigVars()
Get the javascript config vars to include on this page.
isDisabled()
Return whether the output will be completely disabled.
wrapWikiTextAsInterface( $wrapperClass, $text)
Convert wikitext in the user interface language to HTML and add it to the buffer with a <div class="$...
addTemplate(&$template)
Add the output of a QuickTemplate to the output buffer.
setRevisionIsCurrent(bool $isCurrent)
Set whether the revision displayed (as set in ::setRevisionId()) is the latest revision of the page.
getCategories( $type='all')
Get the list of category names this page belongs to.
addWikiTextAsContent( $text, $linestart=true, ?PageReference $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
getBottomScripts()
JS stuff to put at the bottom of the <body>.
setRobotsOptions(array $options=[])
Set the robots policy with options for the page.
const CSP_META
Output CSP policies as meta tags.
getArticleBodyOnly()
Return whether the output will contain only the body of the article.
versionRequired( $version)
Display an error page indicating that a given version of MediaWiki is required to use it.
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
ResourceLoader $mResourceLoader
addParserOutputMetadata(ParserOutput $parserOutput)
Add all metadata associated with a ParserOutput object, but without the actual HTML.
getSyndicationLinks()
Return URLs for each supported syndication format for this page.
addContentOverrideCallback(callable $callback)
Add a callback for mapping from a Title to a Content object, for things like page preview.
array $mAdditionalHtmlClasses
Additional <html> classes; This should be rarely modified; prefer mAdditionalBodyClasses.
isSyndicated()
Should we output feed links for this page?
addTOCPlaceholder(TOCData $tocData)
Helper function to add a Table of Contents to the output.
buildExemptModules()
Build exempt modules and legacy non-ResourceLoader styles.
string $mLastModified
Used for sending cache control.
clearHTML()
Clear the body HTML.
setPageTitle( $name)
"Page title" means the contents of <h1>.
setCopyright( $hasCopyright)
Set whether the standard copyright should be shown for the current page.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
setPageTitleMsg(Message $msg)
"Page title" means the contents of <h1>.
userCanPreview()
To make it harder for someone to slip a user a fake JavaScript or CSS preview, a random token is asso...
addFeedLink( $format, $href)
Add a feed link to the page header.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
getCanonicalUrl()
Returns the URL to be used for the <link rel=canonical>> if one is set.
static combineWrappedStrings(array $chunks)
Combine WrappedString chunks and filter out empty ones.
isTOCEnabled()
Whether the output has a table of contents when the ToC is rendered inline.
setPrintable()
Set the page as printable, i.e.
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
setProperty( $name, $value)
Set an additional output property.
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
haveCacheVaryCookies()
Check if the request has a cache-varying cookie header If it does, it's very important that we don't ...
loadSkinModules( $sk)
Transfer styles and JavaScript modules from skin.
addHtmlClasses( $classes)
Add a class to the <html> element.
returnToMain( $unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request,...
__construct(IContextSource $context)
Constructor for OutputPage.
enableOOUI()
Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with MediaW...
getVaryHeader()
Return a Vary: header on which to vary caches.
showNewSectionLink()
Show an "add new section" link?
addElement( $element, array $attribs=[], $contents='')
Shortcut for adding an Html::element via addHTML.
array $mAdditionalBodyClasses
Additional <body> classes; there are also <body> classes from other sources.
array $styles
An array of stylesheet filenames (relative from skins path), with options for CSS media,...
getHTML()
Get the body HTML.
parseAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language and return the HTML.
int $mCdnMaxageLimit
Upper limit on mCdnMaxage.
addParserOutputText( $text, $poOptions=[])
Add the HTML associated with a ParserOutput object, without any metadata.
couldBePublicCached()
Whether the output might become publicly cached.
output( $return=false)
Finally, all the text has been munged and accumulated into the object, let's actually output it:
addBacklinkSubtitle(PageReference $title, $query=[])
Add a subtitle containing a backlink to a page.
setArticleRelated( $newVal)
Set whether this page is related an article on the wiki Setting false will cause the change of "artic...
prependHTML( $text)
Prepend $text to the body HTML.
showPermissionStatus(PermissionStatus $status, $action=null)
Output a standard permission error page.
enableClientCache()
Do not send nocache headers.
const CSP_HEADERS
Output CSP policies as headers.
addCategoryLinksToLBAndGetResult(array $categories)
reduceAllowedModules( $type, $level)
Limit the highest level of CSS/JS untrustworthiness allowed.
prepareErrorPage()
Prepare this object to display an error page; disable caching and indexing, clear the current text an...
considerCacheSettingsFinal()
Set the expectation that cache control will not change after this point.
parseInlineAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language, strip paragraph wrapper, and return the HTML.
addScriptFile( $file, $unused=null)
Add a JavaScript file to be loaded as <script> on this page.
setRobotPolicy( $policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
array $mAllowedModules
What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
setCanonicalUrl( $url)
Set the URL to be used for the <link rel=canonical>>.
showPendingTakeover( $fallbackUrl, $msg,... $params)
Output a standard "wait for takeover" warning.
addLinkHeader( $header)
Add an HTTP Link: header.
addMeta( $name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
getPageTitle()
Return the "page title", i.e.
addHTML( $text)
Append $text to the body HTML.
string[][] $mMetatags
Should be private.
getDisplayTitle()
Returns page display title.
getRevisionId()
Get the displayed revision ID.
getRobotPolicy()
Get the current robot policy for the page as a string in the form <index policy>,<follow policy>.
addWikiTextAsInterface( $text, $linestart=true, ?PageReference $title=null)
Convert wikitext in the user interface language to HTML and add it to the buffer.
addInlineStyle( $style_css, $flip='noflip')
Adds inline CSS styles Internal use only.
addLink(array $linkarr)
Add a new <link> tag to the page header.
styleLink( $style, array $options)
Generate <link> tags for stylesheets.
setCategoryLinks(array $categories)
Reset the category links (but not the category list) and add $categories.
getFollowPolicy()
Get the current follow policy for the page as a string.
static buildBacklinkSubtitle(PageReference $page, $query=[])
Build message object for a subtitle containing a backlink to a page.
getIndexPolicy()
Get the current index policy for the page as a string.
getFileSearchOptions()
Get the files used on this page.
addContentOverride( $target, Content $content)
Force the given Content object for the given page, for things like page preview.
isArticleRelated()
Return whether this page is related an article on the wiki.
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
clearSubtitle()
Clear the subtitles.
addSubtitle( $str)
Add $str to the subtitle.
isRevisionCurrent()
Whether the revision displayed is the latest revision of the page.
sendCacheControl()
Send cache control HTTP headers.
static transformFilePath( $remotePathPrefix, $localPath, $file)
Utility method for transformResourceFilePath().
addCategoryLinks(array $categories)
Add an array of categories, with names in the keys.
getCategoryLinks()
Get the list of category links, in a 2-D array with the following format: $arr[$type][] = $link,...
getTemplateIds()
Get the templates used on this page.
getFileVersion()
Get the displayed file version.
addModuleStyles( $modules)
Load the styles of one or more style-only ResourceLoader modules on this page.
getPreventClickjacking()
Get the prevent-clickjacking flag.
addScript( $script)
Add raw HTML to the list of scripts (including <script> tag, etc.) Internal use only.
setStatusCode( $statusCode)
Set the HTTP status code to send with the output.
setTOCData(TOCData $tocData)
Adds Table of Contents data to OutputPage from ParserOutput.
addHelpLink( $to, $overrideBaseUrl=false)
Adds a help link with an icon via page indicators.
int $mCdnMaxage
Cache stuff.
getAdvertisedFeedTypes()
Return effective list of advertised feed types.
getMetadata()
Return a ParserOutput that can be used to set metadata properties for the current page.
setArticleBodyOnly( $only)
Set whether the output should only contain the body of the article, without any skin,...
addHeadItem( $name, $value)
Add or replace a head item to the output.
getModuleStyles( $filter=false,... $args)
Get the list of style-only modules to load on this page.
addReturnTo( $title, array $query=[], $text=null, $options=[])
Add a "return to" link pointing to a specified title.
addWikiMsgArray( $name, $args)
Add a wikitext-formatted message to the output.
isPrintable()
Return whether the page is "printable".
addInlineScript( $script)
Add a self-contained script tag with the given contents Internal use only.
setFileVersion( $file)
Set the displayed file version.
checkLastModified( $timestamp)
checkLastModified tells the client to use the client-cached page if possible.
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:64
Set options of the Parser.
setAllowUnsafeRawHtml( $x)
If the wiki is configured to allow raw html ($wgRawHtml = true) is it allowed in the specific case of...
setSuppressSectionEditLinks()
Suppress section edit links in the output.
setIsMessage( $x)
Set whether we are parsing a message.
setInterfaceMessage( $x)
Parsing an interface message in the user language?
ParserOutput is a rendering of a Content object or a message.
getExtraCSPDefaultSrcs()
Get extra Content-Security-Policy 'default-src' directives.
getJsConfigVars(bool $showStrategyKeys=false)
getContentHolderText()
Returns the body fragment text of the ParserOutput.
clearWrapperDivClass()
Clears the CSS class to use for the wrapping div, effectively disabling the wrapper div until addWrap...
getPreventClickjacking()
Get the prevent-clickjacking flag.
getExtraCSPStyleSrcs()
Get extra Content-Security-Policy 'style-src' directives.
getWrapperDivClass()
Returns the class (or classes) to be used with the wrapper div for this output.
setRawText(?string $text)
Set the raw text of the ParserOutput.
getLinkList(string|ParserOutputLinkTypes $linkType, ?int $onlyNamespace=null)
Get a list of links of the given type.
hasImages()
Return true if there are image dependencies registered for this ParserOutput.
getOutputFlag(ParserOutputFlags|string $name)
Provides a uniform interface to various boolean flags stored in the ParserOutput.
setSections(array $sectionArray)
getExtraCSPScriptSrcs()
Get extra Content-Security-Policy 'script-src' directives.
getRawText()
Get the cacheable text with <mw:editsection> markers still in it.
setOutputFlag(ParserOutputFlags|string $name, bool $val=true)
Provides a uniform interface to various boolean flags stored in the ParserOutput.
addWrapperDivClass( $class)
Add a CSS class to use for the wrapping div.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:135
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:32
A StatusValue for permission errors.
Load JSON files, and uses a Processor to extract information.
Handle sending Content-Security-Policy headers.
WebRequest clone which takes values from a provided array.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form,...
Load and configure a ResourceLoader client on an HTML page.
setConfig(array $vars)
Set mw.config variables.
Context object that contains information about the state of a specific ResourceLoader web request.
Definition Context.php:32
ResourceLoader is a loading system for JavaScript and CSS resources.
PHP-based skin template that holds data.
The base class for all skins.
Definition Skin.php:52
getPageClasses( $title)
TODO: document.
Definition Skin.php:701
getOptions()
Get current skin's options.
Definition Skin.php:2452
getHtmlElementAttributes()
Return values for <html> element.
Definition Skin.php:743
isResponsive()
Indicates if this skin is responsive.
Definition Skin.php:369
Parent class for all special pages.
Represents the target of a wiki link.
Represents a title within MediaWiki.
Definition Title.php:69
Library for creating and parsing MW-style timestamps.
getMessages(?string $type=null)
Returns a list of error messages, optionally only those of the given type.
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:18
Value object representing a message parameter with one of the types from {.
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> false, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 250, 300,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'EnableSpecialMute'=> false, 'EnableUserEmailMuteList'=> false, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ImageLinksSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ImageLinksSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'RCEngines' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], 'required' => [ 'url', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Interface for configuration instances.
Definition Config.php:18
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Content objects represent page content, e.g.
Definition Content.php:28
Interface for objects which can provide a MediaWiki context on request.
getConfig()
Get the site configuration.
Represents the target of a wiki link.
Data record representing a page that is (or used to be, or could be) an editable page on a wiki.
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
getNamespace()
Returns the page's namespace number.
getDBkey()
Get the page title in DB key form.
MediaWiki\Session entry point interface.
Result wrapper for grabbing data queried from an IDatabase object.
msg( $key,... $params)