MediaWiki master
OutputPage.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Output;
10
11use CSSJanus;
12use Exception;
13use InvalidArgumentException;
21use MediaWiki\Debug\DeprecationHelper;
24use 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 sort( $languageLinks );
2533 // This hook can be used to remove/replace language links
2534 $this->getHookRunner()->onLanguageLinks( $this->getTitle(), $languageLinks, $linkFlags );
2535 $this->metadata->clearLanguageLinks();
2536 foreach ( ( $languageLinks ?? [] ) as $l ) {
2537 $this->metadata->addLanguageLink( $l );
2538 }
2539
2540 $this->getHookRunner()->onOutputPageParserOutput( $this, $parserOutput );
2541
2542 // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
2543 // so that extensions may modify ParserOutput to toggle TOC.
2544 // This cannot be moved to addParserOutputText because that is not
2545 // called by EditPage for Preview.
2546
2547 // ParserOutputFlags::SHOW_TOC is used to indicate whether the TOC
2548 // should be shown (or hidden) in the output.
2549 $this->mEnableTOC = $this->mEnableTOC ||
2550 $parserOutput->getOutputFlag( ParserOutputFlags::SHOW_TOC );
2551 // Uniform handling of all boolean flags: they are OR'ed together
2552 // (See ParserOutput::collectMetadata())
2553 $flags =
2554 array_flip( $parserOutput->getAllFlags() ) +
2555 array_flip( ParserOutputFlags::values() );
2556 foreach ( $flags as $name => $ignore ) {
2557 if ( $parserOutput->getOutputFlag( $name ) ) {
2558 $this->mOutputFlags[$name] = true;
2559 }
2560 }
2561 }
2562
2563 private function getParserOutputText(
2564 ParserOutput $parserOutput,
2565 ParserOptions $parserOptions,
2566 array $poOptions
2567 ): string {
2568 // Add default options from the skin
2569 $skin = $this->getSkin();
2570 $skinOptions = $skin->getOptions();
2571 $oldText = $parserOutput->getRawText();
2572 $poOptions += [
2573 // T371022
2574 'allowClone' => false,
2575 'skin' => $skin,
2576 'injectTOC' => $skinOptions['toc'],
2577 ];
2578 $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
2579 // Note: this path absolutely expects the metadata of $parserOutput to be mutated by the pipeline,
2580 // but the raw text should not be, see T353257
2581 // TODO T371008 consider if using the Content framework makes sense instead of creating the pipeline
2582 $text = $pipeline->run(
2583 $parserOutput,
2584 // This should be the same parser options that generated
2585 // $parserOutput
2586 $parserOptions,
2587 $poOptions
2588 )->getContentHolderText();
2589 $parserOutput->setRawText( $oldText );
2590 return $text;
2591 }
2592
2603 public function addParserOutputContent( ParserOutput $parserOutput, $parserOptions = null, $poOptions = null ) {
2604 // For backward compatibility, accept $poOptions in the $parserOptions
2605 // argument. This will also trigger the deprecation warning below.
2606 if ( is_array( $parserOptions ) ) {
2607 $poOptions = $parserOptions;
2608 $parserOptions = null;
2609 }
2610 if ( $parserOptions === null ) {
2611 wfDeprecated( __METHOD__ . ' without ParserOptions argument', '1.44' );
2612 // XXX: This isn't guaranteed to be the same parser options that
2613 // generated $parserOutput.
2614 $parserOptions = $this->internalParserOptions( false );
2615 }
2616 $poOptions ??= [];
2617 $text = $this->getParserOutputText( $parserOutput, $parserOptions, $poOptions );
2618 $this->addParserOutputText( $text, $poOptions );
2619
2620 $this->addModules( $parserOutput->getModules() );
2621 $this->addModuleStyles( $parserOutput->getModuleStyles() );
2622
2623 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
2624 }
2625
2633 public function addParserOutputText( $text, $poOptions = [] ) {
2634 if ( $text instanceof ParserOutput ) {
2635 wfDeprecated( __METHOD__ . ' with ParserOutput as first arg', '1.42' );
2636 $parserOptions = $this->internalParserOptions( false );
2637 $text = $this->getParserOutputText( $text, $parserOptions, $poOptions );
2638 }
2639 $this->getHookRunner()->onOutputPageBeforeHTML( $this, $text );
2640 $this->addHTML( $text );
2641 }
2642
2651 public function addParserOutput( ParserOutput $parserOutput, $parserOptions = null, $poOptions = null ) {
2652 // For backward compatibility, accept $poOptions in the $parserOptions
2653 // argument. This will also trigger the deprecation warning below.
2654 if ( is_array( $parserOptions ) ) {
2655 $poOptions = $parserOptions;
2656 $parserOptions = null;
2657 }
2658 if ( $parserOptions === null ) {
2659 wfDeprecated( __METHOD__ . ' without ParserOptions argument', '1.44' );
2660 // XXX: This isn't guaranteed to be the same parser options that
2661 // generated $parserOutput.
2662 $parserOptions = $this->internalParserOptions( false );
2663 }
2664 $poOptions ??= [];
2665
2667 $text = $this->getParserOutputText( $parserOutput, $parserOptions, $poOptions );
2668 $this->addParserOutputMetadata( $parserOutput );
2669 $this->addParserOutputText( $text, $poOptions );
2670 }
2671
2672 public function addPostProcessedParserOutput( ParserOutput $parserOutput ) {
2673 $this->addParserOutputMetadata( $parserOutput );
2674 $this->addParserOutputText( $parserOutput->getContentHolderText() );
2675 }
2676
2682 public function addTemplate( &$template ) {
2683 $this->addHTML( $template->getHTML() );
2684 }
2685
2696 public function parseAsContent( $text, $linestart = true ) {
2697 $title = $this->getTitle();
2698 if ( $title === null ) {
2699 throw new RuntimeException( 'No title in ' . __METHOD__ );
2700 }
2701 [ $po, ] = $this->parseInternal(
2702 $text, $title, $linestart,
2703 $this->internalParserOptions( false ),
2704 /*allowTOC*/ false, /*wrapperDivClass*/ null, /*postprocess*/ true
2705 );
2706 return $po->getContentHolderText();
2707 }
2708
2720 public function parseAsInterface( $text, $linestart = true ) {
2721 $title = $this->getTitle();
2722 if ( $title === null ) {
2723 throw new RuntimeException( 'No title in ' . __METHOD__ );
2724 }
2725 [ $po, ] = $this->parseInternal(
2726 $text, $title, $linestart,
2727 $this->internalParserOptions( true ),
2728 /*allowTOC*/ false, /*wrapperDivClass*/ null, /*postprocess*/ true
2729 );
2730 return $po->getContentHolderText();
2731 }
2732
2746 public function parseInlineAsInterface( $text, $linestart = true ) {
2747 return Parser::stripOuterParagraph(
2748 $this->parseAsInterface( $text, $linestart )
2749 );
2750 }
2751
2764 private function parseInternal(
2765 string $text, PageReference $title,
2766 bool $linestart, ParserOptions $popts, bool $allowTOC, ?string $wrapperClass,
2767 bool $postprocess
2768 ) {
2769 $parserOutput = MediaWikiServices::getInstance()->getParserFactory()->getInstance()
2770 ->parse(
2771 $text, $title, $popts,
2772 $linestart, true, $this->mRevisionId
2773 );
2774
2775 // Don't include default mw-parser-output wrap class, just use our own
2776 $parserOutput->clearWrapperDivClass();
2777 if ( $wrapperClass !== null ) {
2778 $parserOutput->addWrapperDivClass( $wrapperClass );
2779 }
2780
2781 if ( !$allowTOC ) {
2782 $parserOutput->setOutputFlag( ParserOutputFlags::NO_TOC );
2783 $parserOutput->setSections( [] );
2784 }
2785
2786 if ( $postprocess ) {
2787 $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
2788 // TODO T371008 consider if using the Content framework makes sense instead of creating the pipeline
2789 $parserOutput = $pipeline->run(
2790 $parserOutput, $popts, [
2791 'userLang' => $this->getContext()->getLanguage(),
2792 ]
2793 );
2794 }
2795
2796 return [ $parserOutput, $popts ];
2797 }
2798
2804 public function setCdnMaxage( $maxage ) {
2805 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2806 }
2807
2817 public function lowerCdnMaxage( $maxage ) {
2818 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2819 $this->setCdnMaxage( $this->mCdnMaxage );
2820 }
2821
2834 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2835 $minTTL = $minTTL ?: 60;
2836 $maxTTL = $maxTTL ?: $this->getConfig()->get( MainConfigNames::CdnMaxAge );
2837
2838 if ( $mtime === null || $mtime === false ) {
2839 // entity does not exist
2840 return;
2841 }
2842
2843 $age = MWTimestamp::time() - (int)wfTimestamp( TS::UNIX, $mtime );
2844 $adaptiveTTL = max( 0.9 * $age, $minTTL );
2845 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2846
2847 $this->lowerCdnMaxage( (int)$adaptiveTTL );
2848 }
2849
2853 public function enableClientCache(): void {
2854 $this->mEnableClientCache = true;
2855 }
2856
2861 public function disableClientCache(): void {
2862 $this->mEnableClientCache = false;
2863 }
2864
2871 public function couldBePublicCached() {
2872 if ( !$this->cacheIsFinal ) {
2873 // - The entry point handles its own caching and/or doesn't use OutputPage.
2874 // (such as load.php, or MediaWiki\Rest\EntryPoint).
2875 //
2876 // - Or, we haven't finished processing the main part of the request yet
2877 // (e.g. Action::show, SpecialPage::execute), and the state may still
2878 // change via enableClientCache().
2879 return true;
2880 }
2881 // e.g. various error-type pages disable all client caching
2882 return $this->mEnableClientCache;
2883 }
2884
2894 public function considerCacheSettingsFinal() {
2895 $this->cacheIsFinal = true;
2896 }
2897
2898 private function getSessionManager(): SessionManagerInterface {
2899 return MediaWikiServices::getInstance()->getSessionManager();
2900 }
2901
2907 public function getCacheVaryCookies() {
2908 if ( self::$cacheVaryCookies === null ) {
2909 $config = $this->getConfig();
2910 self::$cacheVaryCookies = array_values( array_unique( array_merge(
2911 $this->getSessionManager()->getVaryCookies(),
2912 [
2913 'forceHTTPS',
2914 ],
2915 $config->get( MainConfigNames::CacheVaryCookies )
2916 ) ) );
2917 $this->getHookRunner()->onGetCacheVaryCookies( $this, self::$cacheVaryCookies );
2918 }
2919 return self::$cacheVaryCookies;
2920 }
2921
2928 public function haveCacheVaryCookies() {
2929 $request = $this->getRequest();
2930 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2931 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2932 wfDebug( __METHOD__ . ": found $cookieName" );
2933 return true;
2934 }
2935 }
2936 wfDebug( __METHOD__ . ': no cache-varying cookies found' );
2937 return false;
2938 }
2939
2945 public function addVaryHeader( $header ) {
2946 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2947 $this->mVaryHeader[$header] = null;
2948 }
2949 }
2950
2957 public function getVaryHeader() {
2958 // If we vary on cookies, let's make sure it's always included here too.
2959 if ( $this->getCacheVaryCookies() ) {
2960 $this->addVaryHeader( 'Cookie' );
2961 }
2962
2963 foreach ( $this->getSessionManager()->getVaryHeaders() as $header => $_ ) {
2964 $this->addVaryHeader( $header );
2965 }
2966 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2967 }
2968
2974 public function addLinkHeader( $header ) {
2975 $this->mLinkHeader[] = $header;
2976 }
2977
2983 public function getLinkHeader() {
2984 if ( !$this->mLinkHeader ) {
2985 return false;
2986 }
2987
2988 return 'Link: ' . implode( ',', $this->mLinkHeader );
2989 }
2990
2998 private function addAcceptLanguage() {
2999 $title = $this->getTitle();
3000 if ( !$title instanceof Title ) {
3001 return;
3002 }
3003
3004 $languageConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
3005 ->getLanguageConverter( $title->getPageLanguage() );
3006 if ( !$this->getRequest()->getCheck( 'variant' ) && $languageConverter->hasVariants() ) {
3007 $this->addVaryHeader( 'Accept-Language' );
3008 }
3009 }
3010
3030 public function setPreventClickjacking( bool $enable ) {
3031 $this->metadata->setPreventClickjacking( $enable );
3032 }
3033
3041 public function getPreventClickjacking() {
3042 return $this->metadata->getPreventClickjacking();
3043 }
3044
3052 public function getFrameOptions() {
3053 $config = $this->getConfig();
3054 if ( $config->get( MainConfigNames::BreakFrames ) ) {
3055 return 'DENY';
3056 } elseif (
3057 $this->metadata->getPreventClickjacking() &&
3058 $config->get( MainConfigNames::EditPageFrameOptions )
3059 ) {
3060 return $config->get( MainConfigNames::EditPageFrameOptions );
3061 }
3062 return false;
3063 }
3064
3066 private function getReportTo() {
3067 $config = $this->getConfig();
3068
3069 $expiry = $config->get( MainConfigNames::ReportToExpiry );
3070
3071 if ( !$expiry ) {
3072 return false;
3073 }
3074
3075 $endpoints = $config->get( MainConfigNames::ReportToEndpoints );
3076
3077 if ( !$endpoints ) {
3078 return false;
3079 }
3080
3081 $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
3082
3083 foreach ( $endpoints as $endpoint ) {
3084 $output['endpoints'][] = [ 'url' => $endpoint ];
3085 }
3086
3087 return json_encode( $output, JSON_UNESCAPED_SLASHES );
3088 }
3089
3090 private function getFeaturePolicyReportOnly(): string {
3091 $config = $this->getConfig();
3092
3093 $features = $config->get( MainConfigNames::FeaturePolicyReportOnly );
3094 return implode( ';', $features );
3095 }
3096
3100 public function sendCacheControl() {
3101 $response = $this->getRequest()->response();
3102 $config = $this->getConfig();
3103
3104 $this->addVaryHeader( 'Cookie' );
3105 $this->addAcceptLanguage();
3106
3107 # don't serve compressed data to clients who can't handle it
3108 # maintain different caches for logged-in users and non-logged in ones
3109 $response->header( $this->getVaryHeader() );
3110
3111 if ( $this->mEnableClientCache ) {
3112 if ( !$config->get( MainConfigNames::UseCdn ) ) {
3113 $privateReason = 'config';
3114 } elseif ( $response->hasCookies() ) {
3115 $privateReason = 'set-cookies';
3116 // The client might use methods other than cookies to appear logged-in.
3117 // E.g. HTTP headers, or query parameter tokens, OAuth, etc.
3118 } elseif ( $this->getRequest()->getSession()->isPersistent() ) {
3119 $privateReason = 'session';
3120 } elseif ( $this->isPrintable() ) {
3121 $privateReason = 'printable';
3122 } elseif ( $this->mCdnMaxage == 0 ) {
3123 $privateReason = 'no-maxage';
3124 } elseif ( $this->haveCacheVaryCookies() ) {
3125 $privateReason = 'cache-vary-cookies';
3126 } else {
3127 $privateReason = false;
3128 }
3129
3130 if ( $privateReason === false ) {
3131 # We'll purge the proxy cache for anons explicitly, but require end user agents
3132 # to revalidate against the proxy on each visit.
3133 # IMPORTANT! The CDN needs to replace the Cache-Control header with
3134 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
3135 wfDebug( __METHOD__ .
3136 ": local proxy caching; {$this->mLastModified} **", 'private' );
3137 # start with a shorter timeout for initial testing
3138 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
3139 $response->header( 'Cache-Control: ' .
3140 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
3141 } else {
3142 # We do want clients to cache if they can, but they *must* check for updates
3143 # on revisiting the page.
3144 wfDebug( __METHOD__ . ": private caching ($privateReason); {$this->mLastModified} **", 'private' );
3145
3146 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
3147 $response->header( 'Cache-Control: private, must-revalidate, max-age=0' );
3148 }
3149 if ( $this->mLastModified ) {
3150 $response->header( "Last-Modified: {$this->mLastModified}" );
3151 }
3152 } else {
3153 wfDebug( __METHOD__ . ': no caching **', 'private' );
3154
3155 # In general, the absence of a last modified header should be enough to prevent
3156 # the client from using its cache. We send a few other things just to make sure.
3157 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
3158 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
3159 }
3160 }
3161
3167 public function loadSkinModules( $sk ) {
3168 foreach ( $sk->getDefaultModules() as $group => $modules ) {
3169 if ( $group === 'styles' ) {
3170 foreach ( $modules as $moduleMembers ) {
3171 $this->addModuleStyles( $moduleMembers );
3172 }
3173 } else {
3174 $this->addModules( $modules );
3175 }
3176 }
3177 }
3178
3186 public function output( $return = false ) {
3187 if ( $this->mDoNothing ) {
3188 return $return ? '' : null;
3189 }
3190
3191 $request = $this->getRequest();
3192 $response = $request->response();
3193 $config = $this->getConfig();
3194
3195 if ( $this->mRedirect != '' ) {
3196 $services = MediaWikiServices::getInstance();
3197 // We do not expand redirect destinations to a full URL, because:
3198 // * Relative URLs are widely supported and valid under the HTTP 1.1 spec (RFC 7131).
3199 // * Expanding a absolute-path URL like "/wiki/Foo" can cause surprising cross-domain
3200 // redirects (T406402).
3201 // * Expanding a relative-path URL like "../Foo" using UrlUtils::expand would corrupt
3202 // the path instead of resolving against the current document location.
3203 // * Expanding a protocol-relative URL like "//example.org/Foo" would compromise
3204 // cacheability of the redirect response.
3205
3206 $redirect = $this->mRedirect;
3207 $code = $this->mRedirectCode;
3208 $content = '';
3209
3210 if ( $this->getHookRunner()->onBeforePageRedirect( $this, $redirect, $code ) ) {
3211 if ( $code == '301' || $code == '303' ) {
3212 if ( !$config->get( MainConfigNames::DebugRedirects ) ) {
3213 $response->statusHeader( (int)$code );
3214 }
3215 $this->mLastModified = wfTimestamp( TS::RFC2822 );
3216 }
3217 if ( $config->get( MainConfigNames::VaryOnXFP ) ) {
3218 $this->addVaryHeader( 'X-Forwarded-Proto' );
3219 }
3220 $this->sendCacheControl();
3221
3222 $response->header( 'Content-Type: text/html; charset=UTF-8' );
3223 if ( $config->get( MainConfigNames::DebugRedirects ) ) {
3224 $url = htmlspecialchars( $redirect );
3225 $content = "<!DOCTYPE html>\n<html>\n<head>\n"
3226 . "<title>Redirect</title>\n</head>\n<body>\n"
3227 . "<p>Location: <a href=\"$url\">$url</a></p>\n"
3228 . "</body>\n</html>\n";
3229
3230 if ( !$return ) {
3231 print $content;
3232 }
3233
3234 } else {
3235 $response->header( 'Location: ' . $redirect );
3236 }
3237 }
3238
3239 return $return ? $content : null;
3240 } elseif ( $this->mStatusCode ) {
3241 $response->statusHeader( $this->mStatusCode );
3242 }
3243
3244 # Buffer output; final headers may depend on later processing
3245 ob_start();
3246
3247 $response->header( 'Content-language: ' .
3248 MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
3249
3250 $linkHeader = $this->getLinkHeader();
3251 if ( $linkHeader ) {
3252 $response->header( $linkHeader );
3253 }
3254
3255 // Prevent framing, if requested
3256 $frameOptions = $this->getFrameOptions();
3257 if ( $frameOptions ) {
3258 $response->header( "X-Frame-Options: $frameOptions" );
3259 }
3260
3261 // Get the Origin-Trial header values. This is used to enable Chrome Origin
3262 // Trials: https://github.com/GoogleChrome/OriginTrials
3263 $originTrials = $config->get( MainConfigNames::OriginTrials );
3264 foreach ( $originTrials as $originTrial ) {
3265 $response->header( "Origin-Trial: $originTrial", false );
3266 }
3267
3268 $reportTo = $this->getReportTo();
3269 if ( $reportTo ) {
3270 $response->header( "Report-To: $reportTo" );
3271 }
3272
3273 $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
3274 if ( $featurePolicyReportOnly ) {
3275 $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
3276 }
3277
3278 if ( $this->mArticleBodyOnly ) {
3279 $response->header( 'Content-type: ' . $config->get( MainConfigNames::MimeType ) . '; charset=UTF-8' );
3280 if ( $this->cspOutputMode === self::CSP_HEADERS ) {
3281 $this->CSP->sendHeaders();
3282 }
3283 echo $this->mBodytext;
3284 } else {
3285 // Enable safe mode if requested (T152169)
3286 if ( $this->getRequest()->getBool( 'safemode' ) ) {
3287 $this->disallowUserJs();
3288 }
3289
3290 $sk = $this->getSkin();
3291 $skinOptions = $sk->getOptions();
3292
3293 if ( $skinOptions['format'] === 'json' ) {
3294 $response->header( 'Content-type: application/json; charset=UTF-8' );
3295 return json_encode( [
3296 '@WARNING' => $this->msg( 'skin-json-warning-message' )->escaped()
3297 ] + $sk->getTemplateData() );
3298 }
3299 $response->header( 'Content-type: ' . $config->get( MainConfigNames::MimeType ) . '; charset=UTF-8' );
3300 $this->loadSkinModules( $sk );
3301
3302 MWDebug::addModules( $this );
3303
3304 // Hook that allows last minute changes to the output page, e.g.
3305 // adding of CSS or JavaScript by extensions, adding CSP sources.
3306 $this->getHookRunner()->onBeforePageDisplay( $this, $sk );
3307
3308 if ( $this->cspOutputMode === self::CSP_HEADERS ) {
3309 $this->CSP->sendHeaders();
3310 }
3311
3312 try {
3313 $sk->outputPageFinal( $this );
3314 } catch ( Exception $e ) {
3315 ob_end_clean(); // bug T129657
3316 throw $e;
3317 }
3318 }
3319
3320 try {
3321 // This hook allows last minute changes to final overall output by modifying output buffer
3322 $this->getHookRunner()->onAfterFinalPageOutput( $this );
3323 } catch ( Exception $e ) {
3324 ob_end_clean(); // bug T129657
3325 throw $e;
3326 }
3327
3328 $this->sendCacheControl();
3329
3330 if ( $return ) {
3331 return ob_get_clean();
3332 } else {
3333 ob_end_flush();
3334 return null;
3335 }
3336 }
3337
3344 public function prepareErrorPage() {
3345 $this->setRobotPolicy( 'noindex,nofollow' );
3346 $this->setArticleRelated( false );
3347 $this->disableClientCache();
3348 $this->mRedirect = '';
3349 $this->clearSubtitle();
3350 $this->clearHTML();
3351 }
3352
3369 public function showErrorPage(
3370 $title, $msg, $params = [], $returnto = null, $returntoquery = null
3371 ) {
3372 if ( !$title instanceof Message ) {
3373 $title = $this->msg( $title );
3374 }
3375
3376 $this->prepareErrorPage();
3377 $this->setPageTitleMsg( $title );
3378
3379 if ( $msg instanceof Message ) {
3380 if ( $params !== [] ) {
3381 trigger_error( 'Argument ignored: $params. The message parameters argument '
3382 . 'is discarded when the $msg argument is a Message object instead of '
3383 . 'a string.', E_USER_NOTICE );
3384 }
3385 $this->addHTML( $msg->parseAsBlock() );
3386 } else {
3387 $this->addWikiMsgArray( $msg, $params );
3388 }
3389
3390 $this->addJsConfigVars( 'wgErrorPageMessageKey', is_string( $msg ) ? $msg : $msg->getKey() );
3391
3392 $this->returnToMain( null, $returnto, $returntoquery );
3393 }
3394
3401 public function showPermissionStatus( PermissionStatus $status, $action = null ) {
3402 Assert::precondition( !$status->isGood(), 'Status must have errors' );
3403
3404 $messages = $status->getMessages();
3405
3406 $services = MediaWikiServices::getInstance();
3407 $groupPermissionsLookup = $services->getGroupPermissionsLookup();
3408
3409 // Display a "login to do this action" error if all of the following conditions are met:
3410 // 1. the user is not logged in as a named user, and so cannot be added to groups
3411 // 2. the only error is insufficient permissions (i.e. no block or something else)
3412 // 3. the error can be avoided simply by logging in
3413
3414 if ( !$this->getUser()->isNamed() && count( $messages ) == 1
3415 && ( $messages[0]->getKey() == 'badaccess-groups' || $messages[0]->getKey() == 'badaccess-group0' )
3416 && ( $groupPermissionsLookup->groupHasPermission( 'user', $action )
3417 || $groupPermissionsLookup->groupHasPermission( 'autoconfirmed', $action ) )
3418 ) {
3419 $displayReturnto = null;
3420
3421 # Due to T34276, if a user does not have read permissions,
3422 # $this->getTitle() will just give Special:Badtitle, which is
3423 # not especially useful as a returnto parameter. Use the title
3424 # from the request instead, if there was one.
3425 $request = $this->getRequest();
3426 $returnto = Title::newFromText( $request->getText( 'title' ) );
3427 $extraParams = [];
3428 if ( $action == 'edit' ) {
3429 $msg = 'whitelistedittext';
3430 $displayReturnto = $returnto;
3431 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
3432 $msg = 'nocreatetext';
3433 } elseif ( $action == 'upload' ) {
3434 $msg = 'uploadnologintext';
3435 } elseif ( $action === 'read' ) {
3436 $msg = 'loginreqpagetext';
3437 $displayReturnto = Title::newMainPage();
3438 } else {
3439 $msg = 'permissionerror-login';
3440 $action_desc = $this->msg( "action-$action" )->plain();
3441 $extraParams = [ $action_desc ];
3442 }
3443
3444 $query = [];
3445
3446 if ( $returnto ) {
3447 $query['returnto'] = $returnto->getPrefixedText();
3448
3449 if ( !$request->wasPosted() ) {
3450 $returntoquery = $request->getQueryValues();
3451 unset( $returntoquery['title'] );
3452 unset( $returntoquery['returnto'] );
3453 unset( $returntoquery['returntoquery'] );
3454 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
3455 }
3456 }
3457
3458 $title = SpecialPage::getTitleFor( 'Userlogin' );
3459 $linkRenderer = $services->getLinkRenderer();
3460 $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
3461 $loginLink = $linkRenderer->makeKnownLink(
3462 $title,
3463 $this->msg( 'loginreqlink' )->text(),
3464 [],
3465 $query
3466 );
3467
3468 $this->prepareErrorPage();
3469 $this->setPageTitleMsg( $this->msg( 'loginreqtitle' ) );
3470 $this->addHTML( $this->msg( $msg )
3471 ->rawParams( $loginLink )
3472 ->params( $loginUrl )
3473 ->params( $extraParams )
3474 ->parse()
3475 );
3476
3477 # Don't return to a page the user can't read otherwise
3478 # we'll end up in a pointless loop
3479 if ( $displayReturnto && $this->getAuthority()->probablyCan( 'read', $displayReturnto ) ) {
3480 $this->returnToMain( null, $displayReturnto );
3481 }
3482 } else {
3483 $this->prepareErrorPage();
3484 $this->setPageTitleMsg( $this->msg( 'permissionserrors' ) );
3485 $this->addWikiTextAsInterface( $this->formatPermissionStatus( $status, $action ) );
3486 }
3487 }
3488
3495 public function versionRequired( $version ) {
3496 $this->prepareErrorPage();
3497 $this->setPageTitleMsg(
3498 $this->msg( 'versionrequired' )->plaintextParams( $version )
3499 );
3500
3501 $this->addWikiMsg( 'versionrequiredtext', $version );
3502 $this->returnToMain();
3503 }
3504
3516 public function formatPermissionStatus( PermissionStatus $status, ?string $action = null ): string {
3517 if ( $status->isGood() ) {
3518 return '';
3519 }
3520
3521 if ( !$status->hasMessagesExcept( 'badaccess-group0' ) ) {
3522 // We don't know why you can't do it; admit that rather than saying the circular
3523 // "you don't have permission to do this because you are not allowed to do this"
3524 if ( $action === null ) {
3525 // We don't know what you were trying to do either.
3526 // At least say just "You are not allowed to do that" once rather than twice
3527 $text = $this->msg( 'badaccess-group0' )->plain();
3528 } else {
3529 $action_desc = $this->msg( "action-$action" )->plain();
3530 $text = $this->msg( 'permissionserrorstext-withaction-noreason', $action_desc )->plain();
3531 }
3532 return Html::rawElement( 'div', [ 'class' => 'permissions-errors' ], $text );
3533 }
3534
3535 $messages = array_map( $this->msg( ... ), $status->getMessages() );
3536
3537 if ( $action == null ) {
3538 $text = $this->msg( 'permissionserrorstext', count( $messages ) )->plain() . "\n\n";
3539 } else {
3540 $action_desc = $this->msg( "action-$action" )->plain();
3541 $text = $this->msg(
3542 'permissionserrorstext-withaction',
3543 count( $messages ),
3544 $action_desc
3545 )->plain() . "\n\n";
3546 }
3547
3548 if ( count( $messages ) > 1 ) {
3549 $text .= Html::openElement( 'ul', [ 'class' => 'permissions-errors' ] );
3550 foreach ( $messages as $message ) {
3551 $text .= Html::rawElement(
3552 'li',
3553 [ 'class' => 'mw-permissionerror-' . $message->getKey() ],
3554 $message->plain()
3555 );
3556 }
3557 $text .= Html::closeElement( 'ul' );
3558 } else {
3559 $text .= Html::openElement( 'div', [ 'class' => 'permissions-errors' ] );
3560 $text .= Html::rawElement(
3561 'div',
3562 [ 'class' => 'mw-permissionerror-' . $messages[ 0 ]->getKey() ],
3563 $messages[ 0 ]->plain()
3564 );
3565 $text .= Html::closeElement( 'div' );
3566 }
3567
3568 return $text;
3569 }
3570
3580 public function showLagWarning( $lag ) {
3581 $config = $this->getConfig();
3582 if ( $lag >= $config->get( MainConfigNames::DatabaseReplicaLagWarning ) ) {
3583 // floor to avoid nano seconds to display
3584 $lag = floor( $lag );
3585 $message = $lag < $config->get( MainConfigNames::DatabaseReplicaLagCritical )
3586 ? 'lag-warn-normal'
3587 : 'lag-warn-high';
3588 // For grep: mw-lag-warn-normal, mw-lag-warn-high
3589 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
3590 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
3591 }
3592 }
3593
3602 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
3603 $linkRenderer = MediaWikiServices::getInstance()
3604 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
3605 $link = $this->msg( 'returnto' )->rawParams(
3606 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
3607 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
3608 }
3609
3618 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
3619 $returnto ??= $this->getRequest()->getText( 'returnto' );
3620
3621 $returntoquery ??= $this->getRequest()->getText( 'returntoquery' );
3622
3623 if ( $returnto === '' ) {
3624 $returnto = Title::newMainPage();
3625 }
3626
3627 if ( is_object( $returnto ) ) {
3628 $linkTarget = TitleValue::castPageToLinkTarget( $returnto );
3629 } else {
3630 $linkTarget = Title::newFromText( $returnto );
3631 }
3632
3633 // We don't want people to return to external interwiki. That
3634 // might potentially be used as part of a phishing scheme
3635 if ( !$linkTarget || $linkTarget->isExternal() ) {
3636 $linkTarget = Title::newMainPage();
3637 }
3638
3639 $this->addReturnTo( $linkTarget, wfCgiToArray( $returntoquery ) );
3640 }
3641
3660 public function showPendingTakeover(
3661 $fallbackUrl, $msg, ...$params
3662 ) {
3663 if ( $msg instanceof Message ) {
3664 if ( $params !== [] ) {
3665 trigger_error( 'Argument ignored: $params. The message parameters argument '
3666 . 'is discarded when the $msg argument is a Message object instead of '
3667 . 'a string.', E_USER_NOTICE );
3668 }
3669 $this->addHTML( $msg->parseAsBlock() );
3670 } else {
3671 $this->addHTML( $this->msg( $msg, ...$params )->parseAsBlock() );
3672 }
3673
3674 // Redirect if the user has no JS (<noscript>)
3675 $escapedUrl = htmlspecialchars( $fallbackUrl );
3676 $this->addHeadItem(
3677 'mw-noscript-fallback',
3678 // https://html.spec.whatwg.org/#attr-meta-http-equiv-refresh
3679 // means that if $fallbackUrl contains unencoded quotation marks
3680 // then this will behave confusingly, but shouldn't break the page
3681 "<noscript><meta http-equiv=\"refresh\" content=\"0; url=$escapedUrl\"></noscript>"
3682 );
3683 // Redirect if the user has no ResourceLoader
3684 $this->addScript( Html::inlineScript(
3685 '(window.NORLQ=window.NORLQ||[]).push(' .
3686 'function(){' .
3687 'location.href=' . json_encode( $fallbackUrl ) . ';' .
3688 '}' .
3689 ');'
3690 ) );
3691 }
3692
3703 private function inDebugMode() {
3704 if ( $this->debugMode === null ) {
3705 $resourceLoaderDebug = $this->getConfig()->get(
3706 MainConfigNames::ResourceLoaderDebug );
3707 $str = $this->getRequest()->getRawVal( 'debug' ) ??
3708 $this->getRequest()->getCookie( 'resourceLoaderDebug', '', $resourceLoaderDebug ? 'true' : '' );
3709 $this->debugMode = RL\Context::debugFromString( $str );
3710 }
3711 return $this->debugMode;
3712 }
3713
3714 private function getRlClientContext(): RL\Context {
3715 if ( !$this->rlClientContext ) {
3716 $query = ResourceLoader::makeLoaderQuery(
3717 [], // modules; not relevant
3718 $this->getLanguage()->getCode(),
3719 $this->getSkin()->getSkinName(),
3720 $this->getUser()->isRegistered() ? $this->getUser()->getName() : null,
3721 null, // version; not relevant
3722 $this->inDebugMode(),
3723 null, // only; not relevant
3724 $this->isPrintable()
3725 );
3726 $this->rlClientContext = new RL\Context(
3727 $this->getResourceLoader(),
3728 new FauxRequest( $query )
3729 );
3730 if ( $this->contentOverrideCallbacks ) {
3731 $this->rlClientContext = new RL\DerivativeContext( $this->rlClientContext );
3732 $this->rlClientContext->setContentOverrideCallback( function ( $page ) {
3733 foreach ( $this->contentOverrideCallbacks as $callback ) {
3734 $content = $callback( $page );
3735 if ( $content !== null ) {
3736 $text = ( $content instanceof TextContent ) ? $content->getText() : '';
3737 if ( preg_match( '/<\/?script/i', $text ) ) {
3738 // Proactively replace this so that we can display a message
3739 // to the user, instead of letting it go to Html::inlineScript(),
3740 // where it would be considered a server-side issue.
3741 $content = new JavaScriptContent(
3742 Html::encodeJsCall( 'mw.log.error', [
3743 "Cannot preview $page due to suspecting script tag inside (T200506)."
3744 ] )
3745 );
3746 }
3747 return $content;
3748 }
3749 }
3750 return null;
3751 } );
3752 }
3753 }
3754 return $this->rlClientContext;
3755 }
3756
3768 public function getRlClient() {
3769 if ( !$this->rlClient ) {
3770 $context = $this->getRlClientContext();
3771 $rl = $this->getResourceLoader();
3772 $this->addModules( [
3773 'user',
3774 'user.options',
3775 ] );
3776 $this->addModuleStyles( [
3777 'site.styles',
3778 'noscript',
3779 'user.styles',
3780 ] );
3781 $generalModules = $this->getModules( /*filter*/ true );
3782 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
3783
3784 // Preload getTitleInfo for:
3785 // * $moduleStyles:
3786 // For isKnownEmpty() calls below when computing $exemptGroups,
3787 // and for isKnownEmpty() calls in RL\ClientHtml when creating stylesheet links.
3788 // * any WikiModule in $generalModules:
3789 // For isKnownEmpty() calls in RL\ClientHtml skipping empty user/embedded JS modules.
3790 $preloadBatch = $moduleStyles;
3791 foreach ( $generalModules as $name ) {
3792 $module = $rl->getModule( $name );
3793 if ( $module && $module instanceof RL\WikiModule ) {
3794 $preloadBatch[] = $name;
3795 }
3796 }
3797 RL\WikiModule::preloadTitleInfo( $context, $preloadBatch );
3798
3799 // Filter out style modules that buildExemptModules() should handle
3800 // instead of RL\ClientHtml
3801 $exemptGroups = [
3802 RL\Module::GROUP_SITE => [],
3803 RL\Module::GROUP_NOSCRIPT => [],
3804 RL\Module::GROUP_PRIVATE => [],
3805 RL\Module::GROUP_USER => []
3806 ];
3807 $exemptStates = [];
3808 $moduleStyles = array_filter( $moduleStyles,
3809 static function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3810 $module = $rl->getModule( $name );
3811 if ( $module ) {
3812 $group = $module->getGroup();
3813 if ( $group !== null && isset( $exemptGroups[$group] ) ) {
3814 // The `noscript` module is excluded from the client
3815 // side registry, no need to set its state either.
3816 // But we still output it. See T291735
3817 if ( $group !== RL\Module::GROUP_NOSCRIPT ) {
3818 $exemptStates[$name] = 'ready';
3819 }
3820 if ( !$module->isKnownEmpty( $context ) ) {
3821 // E.g. Don't output empty <styles>
3822 $exemptGroups[$group][] = $name;
3823 }
3824 return false;
3825 }
3826 }
3827 return true;
3828 }
3829 );
3830 $this->rlExemptStyleModules = $exemptGroups;
3831
3832 $config = $this->getConfig();
3833 // Client preferences are controlled by the skin and specific to unregistered
3834 // users. See mw.user.clientPrefs for details on how this works and how to
3835 // handle registered users.
3836 $clientPrefEnabled = (
3837 $this->getSkin()->getOptions()['clientPrefEnabled'] &&
3838 !$this->getUser()->isNamed()
3839 );
3840 $clientPrefCookiePrefix = $config->get( MainConfigNames::CookiePrefix );
3841
3842 $rlClient = new RL\ClientHtml( $context, [
3843 'target' => $this->getTarget(),
3844 // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3845 // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3846 // modules enqueued for loading on this page is filtered to just those.
3847 // However, to make sure we also apply the restriction to dynamic dependencies and
3848 // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3849 // StartupModule so that the client-side registry will not contain any restricted
3850 // modules either. (T152169, T185303)
3851 'safemode' => ( $this->getAllowedModules( RL\Module::TYPE_COMBINED )
3852 <= RL\Module::ORIGIN_CORE_INDIVIDUAL
3853 ) ? '1' : null,
3854 'clientPrefEnabled' => $clientPrefEnabled,
3855 'clientPrefCookiePrefix' => $clientPrefCookiePrefix,
3856 ] );
3857 $rlClient->setConfig( $this->getJSVars( self::JS_VAR_EARLY ) );
3858 $rlClient->setModules( $generalModules );
3859 $rlClient->setModuleStyles( $moduleStyles );
3860 $rlClient->setExemptStates( $exemptStates );
3861 $this->rlClient = $rlClient;
3862 }
3863 return $this->rlClient;
3864 }
3865
3871 public function headElement( Skin $sk, $includeStyle = true ) {
3872 $config = $this->getConfig();
3873 $userdir = $this->getLanguage()->getDir();
3874 $services = MediaWikiServices::getInstance();
3875 $sitedir = $services->getContentLanguage()->getDir();
3876
3877 $rlHtmlAtribs = $this->getRlClient()->getDocumentAttributes();
3878 $skinHtmlAttribs = $sk->getHtmlElementAttributes();
3879
3880 $lookupService = $services->getUserOptionsLookup();
3881 $user = $this->getUser();
3882 $thumbnailIndex = $lookupService->getOption( $user, 'thumbsize' );
3883 $thumbnailSize = $config->get( 'ThumbLimits' )[ $thumbnailIndex ] ?? 250;
3884 $thumbValue = $thumbnailSize === 250 ? 'standard' : (
3885 $thumbnailSize < 250 ? 'small' : 'large'
3886 );
3887 // Combine the classes from different sources, and convert to a string, which is needed below
3888 $htmlClass = Html::expandClassList( [
3889 Html::expandClassList( $rlHtmlAtribs['class'] ?? [] ),
3890 Html::expandClassList( $skinHtmlAttribs['class'] ?? [] ),
3891 Html::expandClassList( $this->mAdditionalHtmlClasses ),
3892 // This uses `-clientpref-` for now to support future customization for anonymous users.
3893 'skin-theme-clientpref-thumb-' . $thumbValue,
3894 ] );
3895
3896 if ( $htmlClass === '' ) {
3897 $htmlClass = null;
3898 }
3899 $htmlAttribs = array_merge( $rlHtmlAtribs, $skinHtmlAttribs, [ 'class' => $htmlClass ] );
3900
3901 $pieces = [];
3902 $pieces[] = Html::htmlHeader( $htmlAttribs );
3903 $pieces[] = Html::openElement( 'head' );
3904
3905 if ( $this->getHTMLTitle() == '' ) {
3906 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3907 }
3908
3909 if ( !Html::isXmlMimeType( $config->get( MainConfigNames::MimeType ) ) ) {
3910 // Add <meta charset="UTF-8">
3911 // This should be before <title> since it defines the charset used by
3912 // text including the text inside <title>.
3913 // The spec recommends defining XHTML5's charset using the XML declaration
3914 // instead of meta.
3915 // Our XML declaration is output by Html::htmlHeader.
3916 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3917 // https://html.spec.whatwg.org/multipage/semantics.html#charset
3918 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3919 }
3920
3921 $pieces[] = Html::element( 'title', [], $this->getHTMLTitle() );
3922 $pieces[] = $this->getRlClient()->getHeadHtml( $htmlClass );
3923 $pieces[] = $this->buildExemptModules();
3924 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3925 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3926
3927 $pieces[] = Html::closeElement( 'head' );
3928
3929 $skinOptions = $sk->getOptions();
3930 $bodyClasses = array_merge( $this->mAdditionalBodyClasses, $skinOptions['bodyClasses'] );
3931 $bodyClasses[] = 'mediawiki';
3932
3933 # Classes for LTR/RTL directionality support
3934 $bodyClasses[] = $userdir;
3935 $bodyClasses[] = "sitedir-$sitedir";
3936
3937 // See Article:showDiffPage for class to support article diff styling
3938
3939 $underline = $lookupService->getOption( $user, 'underline' );
3940 if ( $underline < 2 ) {
3941 // The following classes can be used here:
3942 // * mw-underline-always
3943 // * mw-underline-never
3944 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3945 }
3946
3947 // Parser feature migration class
3948 // The idea is that this will eventually be removed, after the wikitext
3949 // which requires it is cleaned up.
3950 $bodyClasses[] = 'mw-hide-empty-elt';
3951
3952 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3953 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3954 $bodyClasses[] =
3955 'action-' . Sanitizer::escapeClass( $this->getContext()->getActionName() );
3956
3957 if ( $sk->isResponsive() ) {
3958 $bodyClasses[] = 'skin--responsive';
3959 }
3960
3961 $bodyAttrs = [];
3962 // While the expandClassList() is not strictly needed, it's used for backwards compatibility
3963 // (this used to be built as a string and hooks likely still expect that).
3964 $bodyAttrs['class'] = Html::expandClassList( $bodyClasses );
3965
3966 $this->getHookRunner()->onOutputPageBodyAttributes( $this, $sk, $bodyAttrs );
3967
3968 $pieces[] = Html::openElement( 'body', $bodyAttrs );
3969
3970 // Add dedicated ARIA live region container for notifications to assistive technology users.
3971 // Note that `aria-atomic="false"` and `aria-relevant="additions text"` are the default
3972 // values and therefore not duplicated below.
3973 $pieces[] = Html::rawElement( 'div', [
3974 'id' => 'mw-aria-live-region',
3975 'class' => 'mw-aria-live-region',
3976 'aria-live' => 'polite',
3977 ], '' );
3978
3979 return self::combineWrappedStrings( $pieces );
3980 }
3981
3987 public function getResourceLoader() {
3988 if ( $this->mResourceLoader === null ) {
3989 // Lazy-initialise as needed
3990 $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3991 }
3992 return $this->mResourceLoader;
3993 }
3994
4003 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
4004 // Apply 'origin' filters
4005 $modules = $this->filterModules( (array)$modules, null, $only );
4006
4007 return RL\ClientHtml::makeLoad(
4008 $this->getRlClientContext(),
4009 $modules,
4010 $only,
4011 $extraQuery
4012 );
4013 }
4014
4021 protected static function combineWrappedStrings( array $chunks ) {
4022 // Filter out empty values
4023 $chunks = array_filter( $chunks, 'strlen' );
4024 return WrappedString::join( "\n", $chunks );
4025 }
4026
4033 public function getBottomScripts() {
4034 // Keep the hook appendage separate to preserve WrappedString objects.
4035 // This enables to merge them where possible.
4036 $extraHtml = '';
4037 $this->getHookRunner()->onSkinAfterBottomScripts( $this->getSkin(), $extraHtml );
4038
4039 $chunks = [];
4040 $chunks[] = $this->getRlClient()->getBodyHtml();
4041
4042 // Legacy non-ResourceLoader scripts
4043 $chunks[] = $this->mScripts;
4044
4045 // Keep hostname and backend time as the first variables for quick view-source access.
4046 // These other variables will form a very long inline blob.
4047 $vars = [];
4048 if ( $this->getConfig()->get( MainConfigNames::ShowHostnames ) ) {
4049 $vars['wgHostname'] = wfHostname();
4050 }
4051 $elapsed = $this->getRequest()->getElapsedTime();
4052 // seconds to milliseconds
4053 $vars['wgBackendResponseTime'] = round( $elapsed * 1000 );
4054
4055 $vars += $this->getJSVars( self::JS_VAR_LATE );
4056 if ( $this->limitReportJSData ) {
4057 $vars['wgPageParseReport'] = $this->limitReportJSData;
4058 }
4059
4060 $rlContext = $this->getRlClientContext();
4061 $chunks[] = ResourceLoader::makeInlineScript(
4062 'mw.config.set(' . $rlContext->encodeJson( $vars ) . ');'
4063 );
4064
4065 $chunks = [ self::combineWrappedStrings( $chunks ) ];
4066 if ( $extraHtml !== '' ) {
4067 $chunks[] = $extraHtml;
4068 }
4069
4070 return WrappedString::join( "\n", $chunks );
4071 }
4072
4079 public function getJsConfigVars() {
4080 return $this->mJsConfigVars;
4081 }
4082
4089 public function addJsConfigVars( $keys, $value = null ) {
4090 if ( is_array( $keys ) ) {
4091 foreach ( $keys as $key => $value ) {
4092 $this->mJsConfigVars[$key] = $value;
4093 }
4094 return;
4095 }
4096
4097 $this->mJsConfigVars[$keys] = $value;
4098 }
4099
4118 public function getJSVars( ?int $flag = null ) {
4119 $curRevisionId = 0;
4120 $articleId = 0;
4121 // T23115
4122 $canonicalSpecialPageName = false;
4123 $services = MediaWikiServices::getInstance();
4124
4125 $title = $this->getTitle();
4126 $ns = $title->getNamespace();
4127 $nsInfo = $services->getNamespaceInfo();
4128 $canonicalNamespace = $nsInfo->exists( $ns )
4129 ? $nsInfo->getCanonicalName( $ns )
4130 : $title->getNsText();
4131
4132 $sk = $this->getSkin();
4133 // Get the relevant title so that AJAX features can use the correct page name
4134 // when making API requests from certain special pages (T36972).
4135 $relevantTitle = $sk->getRelevantTitle();
4136
4137 if ( $ns === NS_SPECIAL ) {
4138 [ $canonicalSpecialPageName, ] =
4139 $services->getSpecialPageFactory()->
4140 resolveAlias( $title->getDBkey() );
4141 } elseif ( $this->canUseWikiPage() ) {
4142 $wikiPage = $this->getWikiPage();
4143 // If we already know that the latest revision ID is the same as the revision ID being viewed,
4144 // avoid fetching it again, as it may give inconsistent results (T339164).
4145 if ( $this->isRevisionCurrent() && $this->getRevisionId() ) {
4146 $curRevisionId = $this->getRevisionId();
4147 } else {
4148 $curRevisionId = $wikiPage->getLatest();
4149 }
4150 $articleId = $wikiPage->getId();
4151 }
4152
4153 // ParserOutput informs HTML/CSS via lang/dir attributes.
4154 // We inform JavaScript via mw.config from here.
4155 $lang = $this->getContentLangForJS();
4156
4157 // Pre-process information
4158 $separatorTransTable = $lang->separatorTransformTable();
4159 $separatorTransTable = $separatorTransTable ?: [];
4160 $compactSeparatorTransTable = [
4161 implode( "\t", array_keys( $separatorTransTable ) ),
4162 implode( "\t", $separatorTransTable ),
4163 ];
4164 $digitTransTable = $lang->digitTransformTable();
4165 $digitTransTable = $digitTransTable ?: [];
4166 $compactDigitTransTable = [
4167 implode( "\t", array_keys( $digitTransTable ) ),
4168 implode( "\t", $digitTransTable ),
4169 ];
4170
4171 $user = $this->getUser();
4172
4173 // Internal variables for MediaWiki core
4174 $vars = [
4175 // @internal For mediawiki.page.ready
4176 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
4177
4178 // @internal For jquery.tablesorter
4179 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
4180 'wgDigitTransformTable' => $compactDigitTransTable,
4181 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
4182 'wgMonthNames' => $lang->getMonthNamesArray(),
4183
4184 // @internal For debugging purposes
4185 'wgRequestId' => WebRequest::getRequestId(),
4186 ];
4187
4188 // Start of supported and stable config vars (for use by extensions/gadgets).
4189 $vars += [
4190 'wgCanonicalNamespace' => $canonicalNamespace,
4191 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
4192 'wgNamespaceNumber' => $title->getNamespace(),
4193 'wgPageName' => $title->getPrefixedDBkey(),
4194 'wgTitle' => $title->getText(),
4195 'wgCurRevisionId' => $curRevisionId,
4196 'wgRevisionId' => (int)$this->getRevisionId(),
4197 'wgArticleId' => $articleId,
4198 'wgIsArticle' => $this->isArticle(),
4199 'wgIsRedirect' => $title->isRedirect(),
4200 'wgAction' => $this->getContext()->getActionName(),
4201 'wgUserName' => $user->isAnon() ? null : $user->getName(),
4202 'wgUserGroups' => $services->getUserGroupManager()->getUserEffectiveGroups( $user ),
4203 'wgCategories' => $this->getCategories(),
4204 'wgPageViewLanguage' => $lang->getCode(),
4205 'wgPageContentLanguage' => $lang->getCode(),
4206 'wgPageContentModel' => $title->getContentModel(),
4207 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
4208 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
4209 ];
4210 if ( $user->isRegistered() ) {
4211 $vars['wgUserId'] = $user->getId();
4212 $vars['wgUserIsTemp'] = $user->isTemp();
4213 $vars['wgUserEditCount'] = $user->getEditCount();
4214 $userReg = $user->getRegistration();
4215 $vars['wgUserRegistration'] = $userReg ? (int)wfTimestamp( TS::UNIX, $userReg ) * 1000 : null;
4216 $userFirstReg = $services->getUserRegistrationLookup()->getFirstRegistration( $user );
4217 $vars['wgUserFirstRegistration'] = $userFirstReg ?
4218 (int)wfTimestamp( TS::UNIX, $userFirstReg ) * 1000 : null;
4219 // Get the revision ID of the oldest new message on the user's talk
4220 // page. This can be used for constructing new message alerts on
4221 // the client side.
4222 $userNewMsgRevId = $this->getLastSeenUserTalkRevId();
4223 // Only occupy precious space in the <head> when it is non-null (T53640)
4224 // mw.config.get returns null by default.
4225 if ( $userNewMsgRevId ) {
4226 $vars['wgUserNewMsgRevisionId'] = $userNewMsgRevId;
4227 }
4228 } else {
4229 $tempUserCreator = $services->getTempUserCreator();
4230 if ( $tempUserCreator->isEnabled() ) {
4231 // For logged-out users only (without a temporary account): get the user name that will
4232 // be used for their temporary account, if it has already been acquired.
4233 // This may be used in previews.
4234 $session = $this->getRequest()->getSession();
4235 $vars['wgTempUserName'] = $tempUserCreator->getStashedName( $session );
4236 }
4237 }
4238 $languageConverter = $services->getLanguageConverterFactory()
4239 ->getLanguageConverter( $title->getPageLanguage() );
4240 if ( $languageConverter->hasVariants() ) {
4241 $vars['wgUserVariant'] = $languageConverter->getPreferredVariant();
4242 }
4243 // Same test as SkinTemplate
4244 $vars['wgIsProbablyEditable'] = $this->getAuthority()->probablyCan( 'edit', $title );
4245 $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle &&
4246 $this->getAuthority()->probablyCan( 'edit', $relevantTitle );
4247 $restrictionStore = $services->getRestrictionStore();
4248 foreach ( $restrictionStore->listApplicableRestrictionTypes( $title ) as $type ) {
4249 // Following keys are set in $vars:
4250 // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
4251 $vars['wgRestriction' . ucfirst( $type )] = $restrictionStore->getRestrictions( $title, $type );
4252 }
4253 if ( $title->isMainPage() ) {
4254 $vars['wgIsMainPage'] = true;
4255 }
4256
4257 $relevantUser = $sk->getRelevantUser();
4258 if ( $relevantUser ) {
4259 $vars['wgRelevantUserName'] = $relevantUser->getName();
4260 }
4261 // End of stable config vars
4262
4263 $titleFormatter = $services->getTitleFormatter();
4264
4265 if ( $this->mRedirectedFrom ) {
4266 // @internal For skin JS
4267 $vars['wgRedirectedFrom'] = $titleFormatter->getPrefixedDBkey( $this->mRedirectedFrom );
4268 }
4269
4270 // Allow extensions to add their custom variables to the mw.config map.
4271 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
4272 // page-dependent but site-wide (without state).
4273 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
4274 $this->getHookRunner()->onMakeGlobalVariablesScript( $vars, $this );
4275
4276 // Merge in variables from addJsConfigVars last
4277 $vars = array_merge( $vars, $this->getJsConfigVars() );
4278
4279 // Return only early or late vars if requested
4280 if ( $flag !== null ) {
4281 $lateVarNames =
4282 array_fill_keys( self::CORE_LATE_JS_CONFIG_VAR_NAMES, true ) +
4283 array_fill_keys( ExtensionRegistry::getInstance()->getAttribute( 'LateJSConfigVarNames' ), true );
4284 foreach ( $vars as $name => $_ ) {
4285 // If the variable's late flag doesn't match the requested late flag, unset it
4286 if ( isset( $lateVarNames[ $name ] ) !== ( $flag === self::JS_VAR_LATE ) ) {
4287 unset( $vars[ $name ] );
4288 }
4289 }
4290 }
4291
4292 return $vars;
4293 }
4294
4300 private function getLastSeenUserTalkRevId() {
4301 $services = MediaWikiServices::getInstance();
4302 $user = $this->getUser();
4303 $userHasNewMessages = $services
4304 ->getTalkPageNotificationManager()
4305 ->userHasNewMessages( $user );
4306 if ( !$userHasNewMessages ) {
4307 return null;
4308 }
4309
4310 $timestamp = $services
4311 ->getTalkPageNotificationManager()
4312 ->getLatestSeenMessageTimestamp( $user );
4313 if ( !$timestamp ) {
4314 return null;
4315 }
4316
4317 $revRecord = $services->getRevisionLookup()->getRevisionByTimestamp(
4318 $user->getTalkPage(),
4319 $timestamp
4320 );
4321 return $revRecord ? $revRecord->getId() : null;
4322 }
4323
4333 public function userCanPreview() {
4334 $request = $this->getRequest();
4335 if (
4336 $request->getRawVal( 'action' ) !== 'submit' ||
4337 !$request->wasPosted()
4338 ) {
4339 return false;
4340 }
4341
4342 $user = $this->getUser();
4343
4344 if ( !$user->isRegistered() ) {
4345 // Anons have predictable edit tokens
4346 return false;
4347 }
4348 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
4349 return false;
4350 }
4351
4352 $title = $this->getTitle();
4353 if ( !$this->getAuthority()->probablyCan( 'edit', $title ) ) {
4354 return false;
4355 }
4356
4357 return true;
4358 }
4359
4363 public function getHeadLinksArray() {
4364 $tags = [];
4365 $config = $this->getConfig();
4366
4367 if ( $this->cspOutputMode === self::CSP_META ) {
4368 foreach ( $this->CSP->getDirectives() as $header => $directive ) {
4369 $tags["meta-csp-$header"] = Html::element( 'meta', [
4370 'http-equiv' => $header,
4371 'content' => $directive,
4372 ] );
4373 }
4374 }
4375
4376 $tags['meta-generator'] = Html::element( 'meta', [
4377 'name' => 'generator',
4378 'content' => 'MediaWiki ' . MW_VERSION,
4379 ] );
4380
4381 if ( $config->get( MainConfigNames::ReferrerPolicy ) !== false ) {
4382 // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
4383 // fallbacks should come before the primary value so we need to reverse the array.
4384 foreach ( array_reverse( (array)$config->get( MainConfigNames::ReferrerPolicy ) ) as $i => $policy ) {
4385 $tags["meta-referrer-$i"] = Html::element( 'meta', [
4386 'name' => 'referrer',
4387 'content' => $policy,
4388 ] );
4389 }
4390 }
4391
4392 $p = $this->getRobotsContent();
4393 if ( $p ) {
4394 // http://www.robotstxt.org/wc/meta-user.html
4395 // Only show if it's different from the default robots policy
4396 $tags['meta-robots'] = Html::element( 'meta', [
4397 'name' => 'robots',
4398 'content' => $p,
4399 ] );
4400 }
4401
4402 # Browser based phone number detection
4403 if ( $config->get( MainConfigNames::BrowserFormatDetection ) !== false ) {
4404 $tags['meta-format-detection'] = Html::element( 'meta', [
4405 'name' => 'format-detection',
4406 'content' => $config->get( MainConfigNames::BrowserFormatDetection ),
4407 ] );
4408 }
4409
4410 foreach ( $this->mMetatags as [ $name, $val ] ) {
4411 $attrs = [];
4412 if ( strncasecmp( $name, 'http:', 5 ) === 0 ) {
4413 $name = substr( $name, 5 );
4414 $attrs['http-equiv'] = $name;
4415 } elseif ( strncasecmp( $name, 'og:', 3 ) === 0 ) {
4416 $attrs['property'] = $name;
4417 } else {
4418 $attrs['name'] = $name;
4419 }
4420 $attrs['content'] = $val;
4421 $tagName = "meta-$name";
4422 if ( isset( $tags[$tagName] ) ) {
4423 $tagName .= $val;
4424 }
4425 $tags[$tagName] = Html::element( 'meta', $attrs );
4426 }
4427
4428 foreach ( $this->mLinktags as $tag ) {
4429 $tags[] = Html::element( 'link', $tag );
4430 }
4431
4432 if ( $config->get( MainConfigNames::UniversalEditButton ) && $this->isArticleRelated() ) {
4433 if ( $this->getAuthority()->probablyCan( 'edit', $this->getTitle() ) ) {
4434 $msg = $this->msg( 'edit' )->text();
4435 // Use mime type per https://phabricator.wikimedia.org/T21165#6946526
4436 $tags['universal-edit-button'] = Html::element( 'link', [
4437 'rel' => 'alternate',
4438 'type' => 'application/x-wiki',
4439 'title' => $msg,
4440 'href' => $this->getTitle()->getEditURL(),
4441 ] );
4442 }
4443 }
4444
4445 # Generally, the order of the favicon and apple-touch-icon links
4446 # should not matter, but Konqueror (3.5.9 at least) incorrectly
4447 # uses whichever one appears later in the HTML source. Make sure
4448 # apple-touch-icon is specified first to avoid this.
4449 $appleTouchIconHref = $config->get( MainConfigNames::AppleTouchIcon );
4450 # Browser look for those by default, unnecessary to set a link tag
4451 if (
4452 $appleTouchIconHref !== false &&
4453 $appleTouchIconHref !== '/apple-touch-icon.png' &&
4454 $appleTouchIconHref !== '/apple-touch-icon-precomposed.png'
4455 ) {
4456 $tags['apple-touch-icon'] = Html::element( 'link', [
4457 'rel' => 'apple-touch-icon',
4458 'href' => $appleTouchIconHref
4459 ] );
4460 }
4461
4462 $faviconHref = $config->get( MainConfigNames::Favicon );
4463 # Browser look for those by default, unnecessary to set a link tag
4464 if ( $faviconHref !== false && $faviconHref !== '/favicon.ico' ) {
4465 $tags['favicon'] = Html::element( 'link', [
4466 'rel' => 'icon',
4467 'href' => $faviconHref
4468 ] );
4469 }
4470
4471 # OpenSearch description link
4472 $tags['opensearch'] = Html::element( 'link', [
4473 'rel' => 'search',
4474 'type' => 'application/opensearchdescription+xml',
4475 'href' => wfScript( 'rest' ) . '/v1/search',
4476 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
4477 ] );
4478
4479 $services = MediaWikiServices::getInstance();
4480
4481 # Real Simple Discovery link, provides auto-discovery information
4482 # for the MediaWiki API (and potentially additional custom API
4483 # support such as WordPress or Twitter-compatible APIs for a
4484 # blogging extension, etc)
4485 $tags['rsd'] = Html::element( 'link', [
4486 'rel' => 'EditURI',
4487 'type' => 'application/rsd+xml',
4488 // Output a protocol-relative URL here if $wgServer is protocol-relative.
4489 // Whether RSD accepts relative or protocol-relative URLs is completely
4490 // undocumented, though.
4491 'href' => (string)$services->getUrlUtils()->expand( wfAppendQuery(
4492 wfScript( 'api' ),
4493 [ 'action' => 'rsd' ] ),
4495 ),
4496 ] );
4497
4498 $tags = array_merge(
4499 $tags,
4500 $this->getHeadLinksCanonicalURLArray( $config ),
4501 $this->getHeadLinksAlternateURLsArray(),
4502 $this->getHeadLinksCopyrightArray( $config ),
4503 $this->getHeadLinksSyndicationArray( $config ),
4504 );
4505
4506 // Allow extensions to add, remove and/or otherwise manipulate these links
4507 // If you want only to *add* <head> links, please use the addHeadItem()
4508 // (or addHeadItems() for multiple items) method instead.
4509 // This hook is provided as a last resort for extensions to modify these
4510 // links before the output is sent to client.
4511 $this->getHookRunner()->onOutputPageAfterGetHeadLinksArray( $tags, $this );
4512
4513 return $tags;
4514 }
4515
4535 private function getHeadLinksCanonicalURLArray( Config $config ) {
4536 $tags = [];
4537 $canonicalUrl = $this->mCanonicalUrl;
4538
4539 if ( $config->get( MainConfigNames::EnableCanonicalServerLink ) ) {
4540 $query = [];
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 $urlVariant = $pageLanguageConverter->getURLVariant();
4549
4550 if ( $canonicalUrl !== false ) {
4551 $canonicalUrl = (string)$services->getUrlUtils()->expand( $canonicalUrl, PROTO_CANONICAL );
4552 } elseif ( $this->isArticleRelated() ) {
4553 if ( $isCanonicalUrlAction ) {
4554 $query['action'] = $action;
4555 } elseif ( !$isLangConversionDisabled && $urlVariant ) {
4556 # T54429, T108443: Making canonical URL language-variant-aware.
4557 $query['variant'] = $urlVariant;
4558 }
4559 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
4560 } else {
4561 $reqUrl = $this->getRequest()->getRequestURL();
4562 $canonicalUrl = (string)$services->getUrlUtils()->expand( $reqUrl, PROTO_CANONICAL );
4563 }
4564 }
4565
4566 if ( $canonicalUrl !== false ) {
4567 $tags['link-canonical'] = Html::element( 'link', [
4568 'rel' => 'canonical',
4569 'href' => $canonicalUrl
4570 ] );
4571 }
4572
4573 return $tags;
4574 }
4575
4584 private function getHeadLinksAlternateURLsArray() {
4585 $tags = [];
4586 $languageUrls = [];
4587 $action = $this->getContext()->getActionName();
4588 $isCanonicalUrlAction = in_array( $action, [ 'history', 'info' ] );
4589 $services = MediaWikiServices::getInstance();
4590 $languageConverterFactory = $services->getLanguageConverterFactory();
4591 $isLangConversionDisabled = $languageConverterFactory->isConversionDisabled();
4592 $pageLang = $this->getTitle()->getPageLanguage();
4593 $pageLanguageConverter = $languageConverterFactory->getLanguageConverter( $pageLang );
4594
4595 # Language variants
4596 if (
4597 $this->isArticleRelated() &&
4598 !$isCanonicalUrlAction &&
4599 $pageLanguageConverter->hasVariants() &&
4600 !$isLangConversionDisabled
4601 ) {
4602 $variants = $pageLanguageConverter->getVariants();
4603 foreach ( $variants as $variant ) {
4604 $bcp47 = LanguageCode::bcp47( $variant );
4605 $languageUrls[$bcp47] = $this->getTitle()
4606 ->getFullURL( [ 'variant' => $variant ], false, PROTO_CURRENT );
4607 }
4608 }
4609
4610 # Alternate URLs for interlanguage links would be handeled in HTML body tag instead of
4611 # head tag, see T326829.
4612
4613 if ( $languageUrls ) {
4614 # Force the alternate URL of page language code to be self.
4615 # T123901, T305540, T108443: Override mixed-variant variant link in language variant links.
4616 $currentUrl = $this->getTitle()->getFullURL( [], false, PROTO_CURRENT );
4617 $pageLangCodeBcp47 = LanguageCode::bcp47( $pageLang->getCode() );
4618 $languageUrls[$pageLangCodeBcp47] = $currentUrl;
4619
4620 ksort( $languageUrls );
4621
4622 # Also add x-default link per https://support.google.com/webmasters/answer/189077?hl=en
4623 $languageUrls['x-default'] = $currentUrl;
4624
4625 # Process all of language variants and interlanguage links
4626 foreach ( $languageUrls as $bcp47 => $languageUrl ) {
4627 $bcp47lowercase = strtolower( $bcp47 );
4628 $tags['link-alternate-language-' . $bcp47lowercase] = Html::element( 'link', [
4629 'rel' => 'alternate',
4630 'hreflang' => $bcp47,
4631 'href' => $languageUrl,
4632 ] );
4633 }
4634 }
4635
4636 return $tags;
4637 }
4638
4645 private function getHeadLinksCopyrightArray( Config $config ) {
4646 $tags = [];
4647
4648 if ( $this->copyrightUrl !== null ) {
4649 $copyright = $this->copyrightUrl;
4650 } else {
4651 $copyright = '';
4652 if ( $config->get( MainConfigNames::RightsPage ) ) {
4653 $copy = Title::newFromText( $config->get( MainConfigNames::RightsPage ) );
4654
4655 if ( $copy ) {
4656 $copyright = $copy->getLocalURL();
4657 }
4658 }
4659
4660 if ( !$copyright && $config->get( MainConfigNames::RightsUrl ) ) {
4661 $copyright = $config->get( MainConfigNames::RightsUrl );
4662 }
4663 }
4664
4665 if ( $copyright ) {
4666 $tags['copyright'] = Html::element( 'link', [
4667 'rel' => 'license',
4668 'href' => $copyright
4669 ] );
4670 }
4671
4672 return $tags;
4673 }
4674
4681 private function getHeadLinksSyndicationArray( Config $config ) {
4682 if ( !$config->get( MainConfigNames::Feed ) ) {
4683 return [];
4684 }
4685
4686 $tags = [];
4687 $feedLinks = [];
4688
4689 foreach ( $this->getSyndicationLinks() as $format => $link ) {
4690 # Use the page name for the title. In principle, this could
4691 # lead to issues with having the same name for different feeds
4692 # corresponding to the same page, but we can't avoid that at
4693 # this low a level.
4694
4695 $feedLinks[] = $this->feedLink(
4696 $format,
4697 $link,
4698 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
4699 $this->msg(
4700 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
4701 )->text()
4702 );
4703 }
4704
4705 # Recent changes feed should appear on every page (except recentchanges,
4706 # that would be redundant). Put it after the per-page feed to avoid
4707 # changing existing behavior. It's still available, probably via a
4708 # menu in your browser. Some sites might have a different feed they'd
4709 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
4710 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
4711 # If so, use it instead.
4712 $sitename = $config->get( MainConfigNames::Sitename );
4713 $overrideSiteFeed = $config->get( MainConfigNames::OverrideSiteFeed );
4714 if ( $overrideSiteFeed ) {
4715 foreach ( $overrideSiteFeed as $type => $feedUrl ) {
4716 // Note, this->feedLink escapes the url.
4717 $feedLinks[] = $this->feedLink(
4718 $type,
4719 $feedUrl,
4720 $this->msg( "site-{$type}-feed", $sitename )->text()
4721 );
4722 }
4723 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
4724 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
4725 foreach ( $this->getAdvertisedFeedTypes() as $format ) {
4726 $feedLinks[] = $this->feedLink(
4727 $format,
4728 $rctitle->getLocalURL( [ 'feed' => $format ] ),
4729 # For grep: 'site-rss-feed', 'site-atom-feed'
4730 $this->msg( "site-{$format}-feed", $sitename )->text()
4731 );
4732 }
4733 }
4734
4735 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
4736 # manipulating or removing existing feed tags. If you want to add new feeds, you should
4737 # use OutputPage::addFeedLink() instead.
4738 $this->getHookRunner()->onAfterBuildFeedLinks( $feedLinks );
4739
4740 $tags += $feedLinks;
4741
4742 return $tags;
4743 }
4744
4753 private function feedLink( $type, $url, $text ) {
4754 return Html::element( 'link', [
4755 'rel' => 'alternate',
4756 'type' => "application/$type+xml",
4757 'title' => $text,
4758 'href' => $url ]
4759 );
4760 }
4761
4771 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
4772 $options = [];
4773 if ( $media ) {
4774 $options['media'] = $media;
4775 }
4776 if ( $condition ) {
4777 $options['condition'] = $condition;
4778 }
4779 if ( $dir ) {
4780 $options['dir'] = $dir;
4781 }
4782 $this->styles[$style] = $options;
4783 }
4784
4793 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
4794 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
4795 # If wanted, and the interface is right-to-left, flip the CSS
4796 $style_css = CSSJanus::transform( $style_css, true, false );
4797 }
4798 $this->mInlineStyles .= Html::inlineStyle( $style_css );
4799 }
4800
4806 protected function buildExemptModules() {
4807 $chunks = [];
4808
4809 // Requirements:
4810 // - Within modules provided by the software (core, skin, extensions),
4811 // styles from skin stylesheets should be overridden by styles
4812 // from modules dynamically loaded with JavaScript.
4813 // - Styles from site-specific, private, and user modules should override
4814 // both of the above.
4815 //
4816 // The effective order for stylesheets must thus be:
4817 // 1. Page style modules, formatted server-side by RL\ClientHtml.
4818 // 2. Dynamically-loaded styles, inserted client-side by mw.loader.
4819 // 3. Styles that are site-specific, private or from the user, formatted
4820 // server-side by this function.
4821 //
4822 // The 'ResourceLoaderDynamicStyles' marker helps JavaScript know where
4823 // point #2 is.
4824
4825 // Add legacy styles added through addStyle()/addInlineStyle() here
4826 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
4827
4828 // Things that go after the ResourceLoaderDynamicStyles marker
4829 $append = [];
4830 $separateReq = [ 'site.styles', 'user.styles' ];
4831 foreach ( $this->rlExemptStyleModules as $moduleNames ) {
4832 if ( $moduleNames ) {
4833 $append[] = $this->makeResourceLoaderLink(
4834 array_diff( $moduleNames, $separateReq ),
4835 RL\Module::TYPE_STYLES
4836 );
4837
4838 foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
4839 // These require their own dedicated request in order to support "@import"
4840 // syntax, which is incompatible with concatenation. (T147667, T37562)
4841 $append[] = $this->makeResourceLoaderLink( $name,
4842 RL\Module::TYPE_STYLES
4843 );
4844 }
4845 }
4846 }
4847 if ( $append ) {
4848 $chunks[] = Html::element(
4849 'meta',
4850 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
4851 );
4852 $chunks = array_merge( $chunks, $append );
4853 }
4854
4855 return self::combineWrappedStrings( $chunks );
4856 }
4857
4861 public function buildCssLinksArray() {
4862 $links = [];
4863
4864 foreach ( $this->styles as $file => $options ) {
4865 $link = $this->styleLink( $file, $options );
4866 if ( $link ) {
4867 $links[$file] = $link;
4868 }
4869 }
4870 return $links;
4871 }
4872
4880 protected function styleLink( $style, array $options ) {
4881 if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
4882 return '';
4883 }
4884
4885 if ( isset( $options['media'] ) ) {
4886 $media = self::transformCssMedia( $options['media'], $this->getRequest() );
4887 if ( $media === null ) {
4888 return '';
4889 }
4890 } else {
4891 $media = 'all';
4892 }
4893
4894 if ( str_starts_with( $style, '/' ) ||
4895 str_starts_with( $style, 'http:' ) ||
4896 str_starts_with( $style, 'https:' )
4897 ) {
4898 $url = $style;
4899 } else {
4900 $config = $this->getConfig();
4901 // Append file hash as query parameter
4902 $url = self::transformResourcePath(
4903 $config,
4904 $config->get( MainConfigNames::StylePath ) . '/' . $style
4905 );
4906 }
4907
4908 $link = Html::linkedStyle( $url, $media );
4909
4910 if ( isset( $options['condition'] ) ) {
4911 $condition = htmlspecialchars( $options['condition'] );
4912 $link = "<!--[if $condition]>$link<![endif]-->";
4913 }
4914 return $link;
4915 }
4916
4938 public static function transformResourcePath( Config $config, $path ) {
4939 $localDir = MW_INSTALL_PATH;
4940 $remotePathPrefix = $config->get( MainConfigNames::ResourceBasePath );
4941 if ( $remotePathPrefix === '' ) {
4942 // The configured base path is required to be empty string for
4943 // wikis in the domain root
4944 $remotePath = '/';
4945 } else {
4946 $remotePath = $remotePathPrefix;
4947 }
4948 if ( !str_starts_with( $path, $remotePath ) || str_starts_with( $path, '//' ) ) {
4949 // - Path is outside wgResourceBasePath, ignore.
4950 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
4951 return $path;
4952 }
4953 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
4954 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
4955 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
4956 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
4957 $uploadPath = $config->get( MainConfigNames::UploadPath );
4958 if ( str_starts_with( $path, $uploadPath ) ) {
4959 $localDir = $config->get( MainConfigNames::UploadDirectory );
4960 $remotePathPrefix = $remotePath = $uploadPath;
4961 }
4962
4963 $path = RelPath::getRelativePath( $path, $remotePath );
4964 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
4965 }
4966
4978 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
4979 // This MUST match the equivalent logic in CSSMin::remapOne()
4980 $localFile = "$localPath/$file";
4981 $url = "$remotePathPrefix/$file";
4982 if ( is_file( $localFile ) ) {
4983 $hash = md5_file( $localFile );
4984 if ( $hash === false ) {
4985 wfLogWarning( __METHOD__ . ": Failed to hash $localFile" );
4986 $hash = '';
4987 }
4988 $url .= '?' . substr( $hash, 0, 5 );
4989 }
4990 return $url;
4991 }
4992
5001 public static function transformCssMedia( $media, $request = null ) {
5002 if ( $request === null ) {
5003 wfDeprecated( __METHOD__ . ' with null $request', '1.46' );
5004 global $wgRequest;
5005 $request = $wgRequest;
5006 }
5007
5008 if ( $request->getBool( 'printable' ) ) {
5009 // When browsing with printable=yes, apply "print" media styles
5010 // as if they are screen styles (no media, media="").
5011 if ( $media === 'print' ) {
5012 return '';
5013 }
5014
5015 // https://www.w3.org/TR/css3-mediaqueries/#syntax
5016 //
5017 // This regex will not attempt to understand a comma-separated media_query_list
5018 // Example supported values for $media:
5019 //
5020 // 'screen', 'only screen', 'screen and (min-width: 982px)' ),
5021 //
5022 // Example NOT supported value for $media:
5023 //
5024 // '3d-glasses, screen, print and resolution > 90dpi'
5025 //
5026 // If it's a "printable" request, we disable all screen stylesheets.
5027 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
5028 if ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
5029 return null;
5030 }
5031 }
5032
5033 return $media;
5034 }
5035
5044 public function addWikiMsg( $name, ...$args ) {
5045 $this->addWikiMsgArray( $name, $args );
5046 }
5047
5057 public function addWikiMsgArray( $name, $args ) {
5058 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
5059 }
5060
5087 public function wrapWikiMsg( $wrap, ...$msgSpecs ) {
5088 $s = $wrap;
5089 foreach ( $msgSpecs as $n => $spec ) {
5090 if ( is_array( $spec ) ) {
5091 $args = $spec;
5092 $name = array_shift( $args );
5093 } else {
5094 $args = [];
5095 $name = $spec;
5096 }
5097 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
5098 }
5099
5100 $title = $this->getTitle();
5101 if ( $title === null ) {
5102 throw new RuntimeException( 'No title in ' . __METHOD__ );
5103 }
5104 $popts = $this->internalParserOptions( true );
5105 // We are *mostly* parsing a message. Other code wants to rely on that. (T395196)
5106 // It would be cleaner if the wrappers were added outside of wikitext parsing, so we could
5107 // really just parse the message, but it seems scary to change that now.
5108 $popts->setIsMessage( true );
5109 $this->addWikiTextTitleInternal( $s, $title, /*linestart*/ true, $popts );
5110 }
5111
5118 public function isTOCEnabled() {
5119 return $this->mEnableTOC;
5120 }
5121
5127 public function addTOCPlaceholder( TOCData $tocData ): void {
5128 $pout = new ParserOutput;
5129 $pout->setTOCData( $tocData );
5130 $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC );
5131 $pout->setRawText( Parser::TOC_PLACEHOLDER );
5132 $this->addParserOutput( $pout, $this->internalParserOptions( false ) );
5133 }
5134
5142 public static function setupOOUI( $skinName = null, $dir = null ) {
5143 if ( !self::$oouiSetupDone ) {
5144 self::$oouiSetupDone = true;
5145 $context = RequestContext::getMain();
5146 $skinName = $context->getSkinName();
5147 $dir = $context->getLanguage()->getDir();
5148 $themes = RL\OOUIFileModule::getSkinThemeMap();
5149 $theme = $themes[$skinName] ?? $themes['default'];
5150 // For example, 'OOUI\WikimediaUITheme'.
5151 $themeClass = "OOUI\\{$theme}Theme";
5152 Theme::setSingleton( new $themeClass() );
5153 Element::setDefaultDir( $dir );
5154 }
5155 }
5156
5162 public static function resetOOUI() {
5163 if ( self::$oouiSetupDone ) {
5164 self::$oouiSetupDone = false;
5165 self::setupOOUI();
5166 }
5167 }
5168
5175 public function enableOOUI() {
5176 self::setupOOUI();
5177 $this->addModuleStyles( [
5178 'oojs-ui-core.styles',
5179 'oojs-ui.styles.indicators',
5180 'mediawiki.widgets.styles',
5181 'oojs-ui-core.icons',
5182 ] );
5183 }
5184
5191 public function getCSP() {
5192 return $this->CSP;
5193 }
5194
5208 public function setCspOutputMode( string $mode ): void {
5209 $this->cspOutputMode = $mode;
5210 }
5211
5221 public function tailElement( $skin ) {
5222 $tail = [
5223 MWDebug::getDebugHTML( $skin ),
5224 $this->getBottomScripts(),
5225 MWDebug::getHTMLDebugLog(),
5226 Html::closeElement( 'body' ),
5227 Html::closeElement( 'html' ),
5228 ];
5229
5230 return WrappedStringList::join( "\n", $tail );
5231 }
5232}
5233
5235class_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:432
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
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:80
exists()
Returns true if file exists in the repository.
Definition File.php:1087
This class is a collection of static functions that serve two purposes:
Definition Html.php:44
Methods for dealing with language codes.
Base class for language-specific code.
Definition Language.php:65
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.
static transformCssMedia( $media, $request=null)
Transform "media" attribute based on request parameters.
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.
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:65
Page existence and metadata cache.
Definition LinkCache.php:54
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.
getOutputFlag(ParserOutputFlags|string $flag)
Provides a uniform interface to various boolean flags stored in the ParserOutput.
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.
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:134
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:54
getPageClasses( $title)
TODO: document.
Definition Skin.php:696
getOptions()
Get current skin's options.
Definition Skin.php:2479
getHtmlElementAttributes()
Return values for <html> element.
Definition Skin.php:738
isResponsive()
Indicates if this skin is responsive.
Definition Skin.php:365
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'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, '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, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], '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, 'createwithcontentmodel' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], '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, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
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)