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 $copyrightUrl;
375
379 private $contentLang;
380
382 private $limitReportJSData = [];
383
385 private $contentOverrides = [];
386
388 private $contentOverrideCallbacks = [];
389
394 private $mLinkHeader = [];
395
399 private $CSP;
400
401 private string $cspOutputMode = self::CSP_HEADERS;
402
411 private ParserOutput $metadata;
412
416 private static $cacheVaryCookies = null;
417
419 private $debugMode = null;
420
426 public function __construct( IContextSource $context ) {
427 $this->deprecatePublicProperty( 'mCategoryLinks', '1.38', __CLASS__ );
428 $this->deprecatePublicProperty( 'mCategories', '1.38', __CLASS__ );
429 $this->deprecatePublicProperty( 'mIndicators', '1.38', __CLASS__ );
430 $this->deprecatePublicProperty( 'mHeadItems', '1.38', __CLASS__ );
431 $this->deprecatePublicProperty( 'mJsConfigVars', '1.38', __CLASS__ );
432 $this->deprecatePublicProperty( 'mEnableClientCache', '1.38', __CLASS__ );
433 $this->deprecatePublicProperty( 'mParserOptions', '1.44', __CLASS__ );
434 $this->setContext( $context );
435 $this->metadata = new ParserOutput( null );
436 // OutputPage default
437 $this->metadata->setPreventClickjacking( true );
438 $this->CSP = new ContentSecurityPolicy(
439 $context->getRequest()->response(),
440 $context->getConfig(),
441 $this->getHookContainer()
442 );
443 $this->metadata->setNoGallery( false );
444 $this->metadata->setNewSection( false );
445 $this->metadata->setHideNewSection( false );
446 $this->metadata->setRevisionTimestamp( null );
447 }
448
455 public function redirect( $url, $responsecode = '302' ) {
456 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
457 $this->mRedirect = str_replace( [ "\r", "\n" ], '', $url );
458 $this->mRedirectCode = (string)$responsecode;
459 }
460
466 public function getRedirect() {
467 return $this->mRedirect;
468 }
469
478 public function setCopyrightUrl( $url ) {
479 $this->copyrightUrl = $url;
480 }
481
487 public function setStatusCode( $statusCode ) {
488 $this->mStatusCode = $statusCode;
489 }
490
495 public function getMetadata(): ParserOutput {
496 // We can deprecate the redundant
497 // methods on OutputPage which simply turn around
498 // and invoke the corresponding method on the metadata
499 // ParserOutput.
500 return $this->metadata;
501 }
502
510 public function addMeta( $name, $val ) {
511 $this->mMetatags[] = [ $name, $val ];
512 }
513
520 public function getMetaTags() {
521 return $this->mMetatags;
522 }
523
531 public function addLink( array $linkarr ) {
532 $this->mLinktags[] = $linkarr;
533 }
534
541 public function getLinkTags() {
542 return $this->mLinktags;
543 }
544
550 public function setCanonicalUrl( $url ) {
551 $this->mCanonicalUrl = $url;
552 }
553
561 public function getCanonicalUrl() {
562 return $this->mCanonicalUrl;
563 }
564
573 public function addScript( $script ) {
574 $this->mScripts .= $script;
575 }
576
585 public function addScriptFile( $file, $unused = null ) {
586 $this->addScript( Html::linkedScript( $file ) );
587 }
588
596 public function addInlineScript( $script ) {
597 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n";
598 }
599
608 protected function filterModules( array $modules, $position = null,
609 $type = RL\Module::TYPE_COMBINED
610 ) {
611 $resourceLoader = $this->getResourceLoader();
612 $filteredModules = [];
613 foreach ( $modules as $val ) {
614 $module = $resourceLoader->getModule( $val );
615 if ( $module instanceof RL\Module
616 && $module->getOrigin() <= $this->getAllowedModules( $type )
617 ) {
618 $filteredModules[] = $val;
619 }
620 }
621 return $filteredModules;
622 }
623
630 public function getModules( $filter = false ) {
631 return $this->getModulesInternal(
632 $filter, 'mModules', RL\Module::TYPE_COMBINED,
633 );
634 }
635
644 private function getModulesInternal(
645 bool $filter, string $param, string $type
646 ) {
647 $modules = array_values( $this->$param );
648 return $filter
649 ? $this->filterModules( $modules, null, $type )
650 : $modules;
651 }
652
658 public function addModules( $modules ) {
659 foreach ( (array)$modules as $moduleName ) {
660 $this->mModules[$moduleName] = $moduleName;
661 }
662 }
663
670 public function getModuleStyles( $filter = false ) {
671 return $this->getModulesInternal(
672 $filter, 'mModuleStyles', RL\Module::TYPE_STYLES
673 );
674 }
675
685 public function addModuleStyles( $modules ) {
686 foreach ( (array)$modules as $moduleName ) {
687 $this->mModuleStyles[$moduleName] = $moduleName;
688 }
689 }
690
694 public function getTarget() {
695 return $this->mTarget;
696 }
697
705 public function addContentOverride( $target, Content $content ) {
706 if ( !$this->contentOverrides ) {
707 // Register a callback for $this->contentOverrides on the first call
708 $this->addContentOverrideCallback( function ( $target ) {
709 $key = $target->getNamespace() . ':' . $target->getDBkey();
710 return $this->contentOverrides[$key] ?? null;
711 } );
712 }
713
714 $key = $target->getNamespace() . ':' . $target->getDBkey();
715 $this->contentOverrides[$key] = $content;
716 }
717
725 public function addContentOverrideCallback( callable $callback ) {
726 $this->contentOverrideCallbacks[] = $callback;
727 }
728
736 public function addHtmlClasses( $classes ) {
737 $this->mAdditionalHtmlClasses = array_merge( $this->mAdditionalHtmlClasses, (array)$classes );
738 }
739
743 public function getHeadItemsArray() {
744 return $this->mHeadItems;
745 }
746
760 public function addHeadItem( $name, $value ) {
761 $this->mHeadItems[$name] = $value;
762 }
763
771 public function addHeadItems( $values ) {
772 $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
773 }
774
781 public function hasHeadItem( $name ) {
782 return isset( $this->mHeadItems[$name] );
783 }
784
791 public function addBodyClasses( $classes ) {
792 $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
793 }
794
802 public function setArticleBodyOnly( $only ) {
803 $this->mArticleBodyOnly = $only;
804 }
805
811 public function getArticleBodyOnly() {
812 return $this->mArticleBodyOnly;
813 }
814
822 public function setProperty( $name, $value ) {
823 $this->mProperties[$name] = $value;
824 }
825
833 public function getProperty( $name ) {
834 return $this->mProperties[$name] ?? null;
835 }
836
848 public function checkLastModified( $timestamp ) {
849 if ( !$timestamp || $timestamp == '19700101000000' ) {
850 wfDebug( __METHOD__ . ': CACHE DISABLED, NO TIMESTAMP' );
851 return false;
852 }
853 $config = $this->getConfig();
854 if ( !$config->get( MainConfigNames::CachePages ) ) {
855 wfDebug( __METHOD__ . ': CACHE DISABLED' );
856 return false;
857 }
858
859 $timestamp = wfTimestamp( TS::MW, $timestamp );
860 $modifiedTimes = [
861 'page' => $timestamp,
862 'user' => $this->getUser()->getTouched(),
863 'epoch' => $config->get( MainConfigNames::CacheEpoch )
864 ];
865 if ( $config->get( MainConfigNames::UseCdn ) ) {
866 // Ensure Last-Modified is never more than "$wgCdnMaxAge" seconds in the past,
867 // because even if the wiki page hasn't been edited, other static resources may
868 // change (site configuration, default preferences, skin HTML, interface messages,
869 // URLs to other files and services) and must roll-over in a timely manner (T46570)
870 $modifiedTimes['sepoch'] = wfTimestamp(
871 TS::MW,
872 time() - $config->get( MainConfigNames::CdnMaxAge )
873 );
874 }
875 $this->getHookRunner()->onOutputPageCheckLastModified( $modifiedTimes, $this );
876
877 $maxModified = max( $modifiedTimes );
878 $this->mLastModified = wfTimestamp( TS::RFC2822, $maxModified );
879
880 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
881 if ( $clientHeader === false ) {
882 wfDebug( __METHOD__ . ': client did not send If-Modified-Since header', 'private' );
883 return false;
884 }
885
886 # IE sends sizes after the date like this:
887 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
888 # this breaks strtotime().
889 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
890
891 // Ignore timezone warning
892 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
893 $clientHeaderTime = @strtotime( $clientHeader );
894 if ( !$clientHeaderTime ) {
895 wfDebug( __METHOD__
896 . ": unable to parse the client's If-Modified-Since header: $clientHeader" );
897 return false;
898 }
899 $clientHeaderTime = wfTimestamp( TS::MW, $clientHeaderTime );
900
901 # Make debug info
902 $info = '';
903 foreach ( $modifiedTimes as $name => $value ) {
904 if ( $info !== '' ) {
905 $info .= ', ';
906 }
907 $info .= "$name=" . wfTimestamp( TS::ISO_8601, $value );
908 }
909
910 wfDebug( __METHOD__ . ': client sent If-Modified-Since: ' .
911 wfTimestamp( TS::ISO_8601, $clientHeaderTime ), 'private' );
912 wfDebug( __METHOD__ . ': effective Last-Modified: ' .
913 wfTimestamp( TS::ISO_8601, $maxModified ), 'private' );
914 if ( $clientHeaderTime < $maxModified ) {
915 wfDebug( __METHOD__ . ": STALE, $info", 'private' );
916 return false;
917 }
918
919 # Not modified
920 # Give a 304 Not Modified response code and disable body output
921 wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
922 ini_set( 'zlib.output_compression', 0 );
923 $this->getRequest()->response()->statusHeader( 304 );
924 $this->sendCacheControl();
925 $this->disable();
926
927 // Don't output a compressed blob when using ob_gzhandler;
928 // it's technically against HTTP spec and seems to confuse
929 // Firefox when the response gets split over two packets.
930 wfResetOutputBuffers( false );
931
932 return true;
933 }
934
941 public function setLastModified( $timestamp ) {
942 $this->mLastModified = wfTimestamp( TS::RFC2822, $timestamp );
943 }
944
952 public function setRobotPolicy( $policy ) {
953 $policy = Article::formatRobotPolicy( $policy );
954
955 if ( isset( $policy['index'] ) ) {
956 $this->setIndexPolicy( $policy['index'] );
957 }
958 if ( isset( $policy['follow'] ) ) {
959 $this->setFollowPolicy( $policy['follow'] );
960 }
961 }
962
969 public function getRobotPolicy() {
970 $indexPolicy = $this->getIndexPolicy();
971 return "{$indexPolicy},{$this->mFollowPolicy}";
972 }
973
979 private function formatRobotsOptions(): string {
980 $options = [];
981 foreach ( $this->mRobotsOptions as $key => $value ) {
982 // Robots meta tags can have directives that are single strings or
983 // have parameters that should be formatted like <directive>:<setting>.
984 // If the options keys are strings, format them accordingly.
985 // https://developers.google.com/search/docs/advanced/robots/robots_meta_tag
986 if ( is_string( $key ) ) {
987 $options[] = "$key:$value";
988 } else {
989 // string cast done by implode below
990 $options[] = $value;
991 }
992 }
993 return implode( ',', $options );
994 }
995
1003 public function setRobotsOptions( array $options = [] ): void {
1004 $this->mRobotsOptions = array_merge( $this->mRobotsOptions, $options );
1005 }
1006
1011 private function getRobotsContent(): string {
1012 $robotOptionString = $this->formatRobotsOptions();
1013 $robotArgs = ( $this->getIndexPolicy() === 'index' &&
1014 $this->mFollowPolicy === 'follow' ) ?
1015 [] :
1016 [
1017 $this->getIndexPolicy(),
1018 $this->mFollowPolicy,
1019 ];
1020 if ( $robotOptionString ) {
1021 $robotArgs[] = $robotOptionString;
1022 }
1023 return implode( ',', $robotArgs );
1024 }
1025
1039 public function setIndexPolicy( $policy ) {
1040 $policy = trim( $policy );
1041 $this->metadata->setIndexPolicy( $policy );
1042 }
1043
1050 public function getIndexPolicy() {
1051 // Unlike ParserOutput, in OutputPage getIndexPolicy() defaults to
1052 // 'index' if unset.
1053 $policy = $this->metadata->getIndexPolicy();
1054 if ( $policy === '' ) {
1055 $policy = 'index';
1056 }
1057 return $policy;
1058 }
1059
1066 public function setFollowPolicy( $policy ) {
1067 $policy = trim( $policy );
1068 if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
1069 $this->mFollowPolicy = $policy;
1070 }
1071 }
1072
1078 public function getFollowPolicy() {
1079 return $this->mFollowPolicy;
1080 }
1081
1088 public function setHTMLTitle( $name ) {
1089 if ( $name instanceof Message ) {
1090 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
1091 } else {
1092 $this->mHTMLtitle = $name;
1093 }
1094 }
1095
1101 public function getHTMLTitle() {
1102 return $this->mHTMLtitle;
1103 }
1104
1108 public function setRedirectedFrom( PageReference $t ) {
1109 $this->mRedirectedFrom = $t;
1110 }
1111
1125 public function setPageTitle( $name ) {
1126 // This is a stronger check than a `string $name` type hint, which automatically stringifies
1127 // stringable objects such as Message when not using strict_types, and we don't want that.
1128 Assert::parameterType( 'string', $name, '$name' );
1129 $this->setPageTitleInternal( $name );
1130 }
1131
1147 public function setPageTitleMsg( Message $msg ): void {
1148 $this->setPageTitleInternal(
1149 $msg->setContext( $this->getContext() )->escaped()
1150 );
1151 }
1152
1153 private function setPageTitleInternal( string $name ): void {
1154 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
1155 # but leave "<i>foobar</i>" alone
1156 $nameWithTags = Sanitizer::removeSomeTags( $name );
1157 $this->mPageTitle = $nameWithTags;
1158
1159 # change "<i>foo&amp;bar</i>" to "foo&bar"
1160 $this->setHTMLTitle(
1161 $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
1162 ->inContentLanguage()
1163 );
1164 }
1165
1171 public function getPageTitle() {
1172 return $this->mPageTitle;
1173 }
1174
1182 public function setDisplayTitle( $html ) {
1183 $this->displayTitle = $html;
1184 }
1185
1194 public function getDisplayTitle() {
1195 $html = $this->displayTitle;
1196 if ( $html === null ) {
1197 return htmlspecialchars( $this->getTitle()->getPrefixedText(), ENT_NOQUOTES );
1198 }
1199
1200 return Sanitizer::removeSomeTags( $html );
1201 }
1202
1211 public function getUnprefixedDisplayTitle() {
1212 $service = MediaWikiServices::getInstance();
1213 $languageConverter = $service->getLanguageConverterFactory()
1214 ->getLanguageConverter( $service->getContentLanguage() );
1215 $text = $this->getDisplayTitle();
1216
1217 // Create a regexp with matching groups as placeholders for the namespace, separator and main text
1218 $pageTitleRegexp = '/' . str_replace(
1219 preg_quote( '(.+?)', '/' ),
1220 '(.+?)',
1221 preg_quote( Parser::formatPageTitle( '(.+?)', '(.+?)', '(.+?)' ), '/' )
1222 ) . '/';
1223 $matches = [];
1224 if ( preg_match( $pageTitleRegexp, $text, $matches ) ) {
1225 // The regexp above could be manipulated by malicious user input,
1226 // sanitize the result just in case
1227 return Sanitizer::removeSomeTags( $matches[3] );
1228 }
1229
1230 $nsPrefix = $languageConverter->convertNamespace(
1231 $this->getTitle()->getNamespace()
1232 ) . ':';
1233 $prefix = preg_quote( $nsPrefix, '/' );
1234
1235 return preg_replace( "/^$prefix/i", '', $text );
1236 }
1237
1241 public function setTitle( PageReference $t ) {
1242 $t = Title::newFromPageReference( $t );
1243
1244 // @phan-suppress-next-next-line PhanUndeclaredMethod
1245 // @fixme Not all implementations of IContextSource have this method!
1246 $this->getContext()->setTitle( $t );
1247 }
1248
1254 public function setSubtitle( $str ) {
1255 $this->clearSubtitle();
1256 $this->addSubtitle( $str );
1257 }
1258
1265 public function addSubtitle( $str ) {
1266 if ( $str instanceof Message ) {
1267 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1268 } else {
1269 $this->mSubtitle[] = $str;
1270 }
1271 }
1272
1281 public static function buildBacklinkSubtitle( PageReference $page, $query = [] ) {
1282 if ( $page instanceof PageRecord || $page instanceof Title ) {
1283 // Callers will typically have a PageRecord
1284 if ( $page->isRedirect() ) {
1285 $query['redirect'] = 'no';
1286 }
1287 } elseif ( $page->getNamespace() !== NS_SPECIAL ) {
1288 // We don't know whether it's a redirect, so add the parameter, just to be sure.
1289 $query['redirect'] = 'no';
1290 }
1291
1292 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1293 return wfMessage( 'backlinksubtitle' )
1294 ->rawParams( $linkRenderer->makeLink( $page, null, [], $query ) );
1295 }
1296
1303 public function addBacklinkSubtitle( PageReference $title, $query = [] ) {
1304 $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1305 }
1306
1310 public function clearSubtitle() {
1311 $this->mSubtitle = [];
1312 }
1313
1317 public function getSubtitle() {
1318 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1319 }
1320
1325 public function setPrintable() {
1326 $this->mPrintable = true;
1327 }
1328
1334 public function isPrintable() {
1335 return $this->mPrintable;
1336 }
1337
1341 public function disable() {
1342 $this->mDoNothing = true;
1343 }
1344
1350 public function isDisabled() {
1351 return $this->mDoNothing;
1352 }
1353
1362 public function setSyndicated( $show = true ) {
1363 if ( $show ) {
1364 $this->setFeedAppendQuery( false );
1365 } else {
1366 $this->mFeedLinks = [];
1367 }
1368 }
1369
1376 protected function getAdvertisedFeedTypes() {
1377 if ( $this->getConfig()->get( MainConfigNames::Feed ) ) {
1378 return $this->getConfig()->get( MainConfigNames::AdvertisedFeedTypes );
1379 } else {
1380 return [];
1381 }
1382 }
1383
1393 public function setFeedAppendQuery( $val ) {
1394 $this->mFeedLinks = [];
1395
1396 foreach ( $this->getAdvertisedFeedTypes() as $type ) {
1397 $query = "feed=$type";
1398 if ( is_string( $val ) ) {
1399 $query .= '&' . $val;
1400 }
1401 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1402 }
1403 }
1404
1411 public function addFeedLink( $format, $href ) {
1412 if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
1413 $this->mFeedLinks[$format] = $href;
1414 }
1415 }
1416
1421 public function isSyndicated() {
1422 return count( $this->mFeedLinks ) > 0;
1423 }
1424
1429 public function getSyndicationLinks() {
1430 return $this->mFeedLinks;
1431 }
1432
1438 public function getFeedAppendQuery() {
1439 return $this->mFeedLinksAppendQuery;
1440 }
1441
1449 public function setArticleFlag( $newVal ) {
1450 $this->mIsArticle = $newVal;
1451 if ( $newVal ) {
1452 $this->mIsArticleRelated = $newVal;
1453 }
1454 }
1455
1462 public function isArticle() {
1463 return $this->mIsArticle;
1464 }
1465
1472 public function setArticleRelated( $newVal ) {
1473 $this->mIsArticleRelated = $newVal;
1474 if ( !$newVal ) {
1475 $this->mIsArticle = false;
1476 }
1477 }
1478
1484 public function isArticleRelated() {
1485 return $this->mIsArticleRelated;
1486 }
1487
1493 public function setCopyright( $hasCopyright ) {
1494 $this->mHasCopyright = $hasCopyright;
1495 }
1496
1506 public function showsCopyright() {
1507 return $this->isArticle() || $this->mHasCopyright;
1508 }
1509
1516 public function addLanguageLinks( array $newLinkArray ) {
1517 # $newLinkArray is in order of appearance on the page;
1518 # deduplicate so only the first for a given prefix is used
1519 # using code in ParserOutput (T26502)
1520 foreach ( $newLinkArray as $t ) {
1521 $this->metadata->addLanguageLink( $t );
1522 }
1523 }
1524
1530 public function getLanguageLinks() {
1531 $result = [];
1532 foreach ( $this->metadata->getLinkList( ParserOutputLinkTypes::LANGUAGE ) as [ 'link' => $link ] ) {
1533 $ll = $link->getInterwiki() . ':' . $link->getDBkey();
1534 # language links can have fragments
1535 if ( $link->getFragment() !== '' ) {
1536 $ll .= '#' . $link->getFragment();
1537 }
1538 $result[] = $ll;
1539 }
1540 return $result;
1541 }
1542
1548 public function addCategoryLinks( array $categories ) {
1549 if ( !$categories ) {
1550 return;
1551 }
1552
1553 $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1554
1555 # Set all the values to 'normal'.
1556 $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1557 $pageData = [];
1558
1559 # Mark hidden categories
1560 foreach ( $res as $row ) {
1561 if ( isset( $row->pp_value ) ) {
1562 $categories[$row->page_title] = 'hidden';
1563 }
1564 // Page exists, cache results
1565 if ( isset( $row->page_id ) ) {
1566 $pageData[$row->page_title] = $row;
1567 }
1568 }
1569
1570 # Add the remaining categories to the skin
1571 $services = MediaWikiServices::getInstance();
1572 $linkRenderer = $services->getLinkRenderer();
1573 $languageConverter = $services->getLanguageConverterFactory()
1574 ->getLanguageConverter( $services->getContentLanguage() );
1575 $collation = $services->getCollationFactory()->getCategoryCollation();
1576 foreach ( $categories as $category => $type ) {
1577 // array keys will cast numeric category names to ints, so cast back to string
1578 $category = (string)$category;
1579 $origcategory = $category;
1580 if ( array_key_exists( $category, $pageData ) ) {
1581 $title = Title::newFromRow( $pageData[$category] );
1582 } else {
1583 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1584 }
1585 if ( !$title ) {
1586 continue;
1587 }
1588 $languageConverter->findVariantLink( $category, $title, true );
1589
1590 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1591 continue;
1592 }
1593 $text = $languageConverter->convertHtml( $title->getText() );
1594 $link = null;
1595 $this->getHookRunner()->onOutputPageRenderCategoryLink( $this, $title->toPageIdentity(), $text, $link );
1596 if ( $link === null ) {
1597 $link = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1598 }
1599 $this->mCategoryData[] = [
1600 'sortKey' => $collation->getSortKey( $text ),
1601 'type' => $type,
1602 'title' => $title->getText(),
1603 'link' => $link,
1604 ];
1605 $this->mCategoriesSorted = false;
1606 // Setting mCategories and mCategoryLinks is redundant here,
1607 // but is needed for compatibility until mCategories and
1608 // mCategoryLinks are made private (T301020)
1609 $this->mCategories[$type][] = $title->getText();
1610 $this->mCategoryLinks[$type][] = $link;
1611 }
1612 }
1613
1618 protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1619 # Add the links to a LinkBatch
1620 $arr = [ NS_CATEGORY => $categories ];
1621 $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
1622 $lb = $linkBatchFactory->newLinkBatch();
1623 $lb->setArray( $arr );
1624
1625 # Fetch existence plus the hiddencat property
1626 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
1627 $fields = array_merge(
1628 LinkCache::getSelectFields(),
1629 [ 'pp_value' ]
1630 );
1631
1632 $res = $dbr->newSelectQueryBuilder()
1633 ->select( $fields )
1634 ->from( 'page' )
1635 ->leftJoin( 'page_props', null, [
1636 'pp_propname' => 'hiddencat',
1637 'pp_page = page_id',
1638 ] )
1639 ->where( $lb->constructSet( 'page', $dbr ) )
1640 ->caller( __METHOD__ )
1641 ->fetchResultSet();
1642
1643 # Add the results to the link cache
1644 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1645 $lb->addResultToCache( $linkCache, $res );
1646
1647 return $res;
1648 }
1649
1659 public function getCategoryLinks() {
1660 $this->maybeSortCategories();
1661 return $this->mCategoryLinks;
1662 }
1663
1673 public function getCategories( $type = 'all' ) {
1674 $this->maybeSortCategories();
1675 if ( $type === 'all' ) {
1676 $allCategories = [];
1677 foreach ( $this->mCategories as $categories ) {
1678 $allCategories = array_merge( $allCategories, $categories );
1679 }
1680 return $allCategories;
1681 }
1682 if ( !isset( $this->mCategories[$type] ) ) {
1683 throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1684 }
1685 return $this->mCategories[$type];
1686 }
1687
1693 private function maybeSortCategories(): void {
1694 if ( $this->mCategoriesSorted ) {
1695 return;
1696 }
1697 // Check wiki configuration...
1698 $sortCategories = $this->getConfig()->get( MainConfigNames::SortedCategories );
1699 // ...but allow override with query parameter.
1700 $sortCategories = $this->getRequest()->getFuzzyBool( 'sortcat', $sortCategories );
1701 if ( $sortCategories ) {
1702 // Primary sort key is the first element of category data, but
1703 // break ties by looking at the other elements.
1704 usort( $this->mCategoryData, static function ( $a, $b ): int {
1705 return $a['type'] <=> $b['type'] ?:
1706 $a['sortKey'] <=> $b['sortKey'] ?:
1707 $a['title'] <=> $b['sortKey'] ?:
1708 $a['link'] <=> $b['link'];
1709 } );
1710 }
1711 // Remove duplicate entries
1712 $this->mCategoryData = array_values( array_unique( $this->mCategoryData, SORT_REGULAR ) );
1713
1714 // Rebuild mCategories and mCategoryLinks
1715 $this->mCategories = [
1716 'hidden' => [],
1717 'normal' => [],
1718 ];
1719 $this->mCategoryLinks = [];
1720 foreach ( $this->mCategoryData as $c ) {
1721 $this->mCategories[$c['type']][] = $c['title'];
1722 if ( $c['link'] !== null ) {
1723 // This test only needed because of ::setCategoryLinks()
1724 $this->mCategoryLinks[$c['type']][] = $c['link'];
1725 }
1726 }
1727 $this->mCategoriesSorted = true;
1728 }
1729
1744 public function setIndicators( array $indicators ) {
1745 $this->mIndicators = $indicators + $this->mIndicators;
1746 // Keep ordered by key
1747 ksort( $this->mIndicators );
1748 }
1749
1758 public function getIndicators(): array {
1759 // Note that some -- but not all -- indicators will be wrapped
1760 // with a class appropriate for user-generated wikitext content
1761 // (usually .mw-parser-output). The exceptions would be an
1762 // indicator added via ::addHelpLink() below, which adds content
1763 // which don't come from the parser and is not user-generated;
1764 // and any indicators added by extensions which may call
1765 // OutputPage::setIndicators() directly. In the latter case the
1766 // caller is responsible for wrapping any parser-generated
1767 // indicators.
1768 return $this->mIndicators;
1769 }
1770
1779 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1780 $this->addModuleStyles( 'mediawiki.helplink' );
1781 $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1782
1783 if ( $overrideBaseUrl ) {
1784 $helpUrl = $to;
1785 } else {
1786 $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1787 $helpUrl = "https://www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1788 }
1789
1790 $link = Html::rawElement(
1791 'a',
1792 [
1793 'href' => $helpUrl,
1794 'target' => '_blank',
1795 'class' => 'mw-helplink',
1796 ],
1797 Html::element( 'span', [ 'class' => 'mw-helplink-icon' ] ) . $text
1798 );
1799
1800 // See note in ::getIndicators() above -- unlike wikitext-generated
1801 // indicators which come from ParserOutput, this indicator will not
1802 // be wrapped.
1803 $this->setIndicators( [ 'mw-helplink' => $link ] );
1804 }
1805
1814 public function disallowUserJs() {
1815 $this->reduceAllowedModules(
1816 RL\Module::TYPE_SCRIPTS,
1817 RL\Module::ORIGIN_CORE_INDIVIDUAL
1818 );
1819
1820 // Site-wide styles are controlled by a config setting, see T73621
1821 // for background on why. User styles are never allowed.
1822 if ( $this->getConfig()->get( MainConfigNames::AllowSiteCSSOnRestrictedPages ) ) {
1823 $styleOrigin = RL\Module::ORIGIN_USER_SITEWIDE;
1824 } else {
1825 $styleOrigin = RL\Module::ORIGIN_CORE_INDIVIDUAL;
1826 }
1827 $this->reduceAllowedModules(
1828 RL\Module::TYPE_STYLES,
1829 $styleOrigin
1830 );
1831 }
1832
1839 public function getAllowedModules( $type ) {
1840 if ( $type == RL\Module::TYPE_COMBINED ) {
1841 return min( array_values( $this->mAllowedModules ) );
1842 } else {
1843 return $this->mAllowedModules[$type] ?? RL\Module::ORIGIN_ALL;
1844 }
1845 }
1846
1856 public function reduceAllowedModules( $type, $level ) {
1857 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1858 }
1859
1866 public function prependHTML( $text ) {
1867 $this->mBodytext = $text . $this->mBodytext;
1868 }
1869
1876 public function addHTML( $text ) {
1877 $this->mBodytext .= $text;
1878 }
1879
1889 public function addElement( $element, array $attribs = [], $contents = '' ) {
1890 $this->addHTML( Html::element( $element, $attribs, $contents ) );
1891 }
1892
1896 public function clearHTML() {
1897 $this->mBodytext = '';
1898 }
1899
1905 public function getHTML() {
1906 return $this->mBodytext;
1907 }
1908
1915 private function internalParserOptions( bool $interface ): ParserOptions {
1916 if ( !$this->getUser()->isSafeToLoad() ) {
1917 // Context user isn't unstubbable yet, so don't try to get a
1918 // ParserOptions for it. And don't cache this ParserOptions
1919 // either.
1920 $parserOptions = ParserOptions::newFromAnon();
1921 } else {
1922 $parserOptions = ParserOptions::newFromContext( $this->getContext() );
1923 }
1924 $parserOptions->setAllowUnsafeRawHtml( false );
1925 $parserOptions->setSuppressSectionEditLinks();
1926 $parserOptions->setInterfaceMessage( $interface );
1927 return $parserOptions;
1928 }
1929
1937 public function setRevisionId( $revid ) {
1938 $val = $revid === null ? null : intval( $revid );
1939 return wfSetVar( $this->mRevisionId, $val, true );
1940 }
1941
1947 public function getRevisionId() {
1948 return $this->mRevisionId;
1949 }
1950
1955 public function setRevisionIsCurrent( bool $isCurrent ): void {
1956 $this->mRevisionIsCurrent = $isCurrent;
1957 }
1958
1965 public function isRevisionCurrent(): bool {
1966 return $this->mRevisionId == 0 || (
1967 $this->mRevisionIsCurrent ?? (
1968 $this->mRevisionId == $this->getTitle()->getLatestRevID()
1969 )
1970 );
1971 }
1972
1981 public function getRevisionTimestamp() {
1982 wfDeprecated( __METHOD__, '1.44' );
1983 return $this->metadata->getRevisionTimestamp();
1984 }
1985
1992 public function setFileVersion( $file ) {
1993 $val = null;
1994 if ( $file instanceof File && $file->exists() ) {
1995 $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1996 }
1997 return wfSetVar( $this->mFileVersion, $val, true );
1998 }
1999
2005 public function getFileVersion() {
2006 return $this->mFileVersion;
2007 }
2008
2015 public function getTemplateIds() {
2016 $result = [];
2017 foreach (
2018 $this->metadata->getLinkList( ParserOutputLinkTypes::TEMPLATE ) as
2019 [ 'link' => $link, 'pageid' => $pageid, 'revid' => $revid ] ) {
2020 $ns = $link->getNamespace();
2021 $dbk = $link->getDBkey();
2022 $result[$ns][$dbk] = $revid;
2023 }
2024 return $result;
2025 }
2026
2033 public function getFileSearchOptions() {
2034 $result = [];
2035 foreach (
2036 $this->metadata->getLinkList( ParserOutputLinkTypes::MEDIA ) as
2037 $linkItem ) {
2038 $link = $linkItem['link'];
2039 unset( $linkItem['link'] );
2040 $result[$link->getDBkey()] = $linkItem + [
2041 'time' => null, 'sha1' => null,
2042 ];
2043 }
2044 return $result;
2045 }
2046
2061 public function addWikiTextAsInterface(
2062 $text, $linestart = true, ?PageReference $title = null
2063 ) {
2064 $title ??= $this->getTitle();
2065 if ( $title === null ) {
2066 throw new RuntimeException( 'No title in ' . __METHOD__ );
2067 }
2068 $this->addWikiTextTitleInternal( $text, $title, $linestart,
2069 $this->internalParserOptions( true ) );
2070 }
2071
2085 public function addWikiTextAsContent(
2086 $text, $linestart = true, ?PageReference $title = null
2087 ) {
2088 $title ??= $this->getTitle();
2089 if ( !$title ) {
2090 throw new RuntimeException( 'No title in ' . __METHOD__ );
2091 }
2092 $this->addWikiTextTitleInternal( $text, $title, $linestart,
2093 $this->internalParserOptions( false ) );
2094 }
2095
2107 private function addWikiTextTitleInternal(
2108 string $text, PageReference $title, bool $linestart, ParserOptions $popts,
2109 ?string $wrapperClass = null
2110 ) {
2111 [ $parserOutput, $parserOptions ] = $this->parseInternal(
2112 $text, $title, $linestart, $popts,
2113 /*allowTOC*/ true, $wrapperClass, /*postprocess*/ false
2114 );
2115
2116 $this->addParserOutput( $parserOutput, $parserOptions, [
2117 ] );
2118 }
2119
2125 public function setTOCData( TOCData $tocData ) {
2126 $this->tocData = $tocData;
2127 }
2128
2134 public function getTOCData(): ?TOCData {
2135 return $this->tocData;
2136 }
2137
2144 public function getOutputFlag( ParserOutputFlags|string $name ): bool {
2145 return $this->metadata->getOutputFlag( $name );
2146 }
2147
2153 public function setContentLangForJS( Bcp47Code $lang ): void {
2154 $this->contentLang = MediaWikiServices::getInstance()->getLanguageFactory()
2155 ->getLanguage( $lang );
2156 }
2157
2172 private function getContentLangForJS(): Language {
2173 if ( !$this->contentLang ) {
2174 // If this is not set, then we're likely not on in a request that renders page content
2175 // (e.g. ViewAction or ApiParse), but rather a different Action or SpecialPage.
2176 // In that case there isn't a main ParserOutput object to represent the page or output.
2177 // But, the skin and frontend code mostly don't make this distinction, and so we still
2178 // need to return something for mw.config.
2179 //
2180 // For historical reasons, the expectation is that:
2181 // * on a SpecialPage, we return the language for the content area just like on a
2182 // page view. SpecialPage content is localised, and so this is the user language.
2183 // * on an Action about a WikiPage, we return the language that content would have
2184 // been shown in, if this were a page view. This is generally the page language
2185 // as stored in the database, except adapted to the current user (e.g. in case of
2186 // translated pages or a language variant preference)
2187 //
2188 // This mess was centralised to here in 2023 (T341244).
2189 $title = $this->getTitle();
2190 if ( $title->isSpecialPage() ) {
2191 // Special pages render in the interface language, based on request context.
2192 // If the user's preference (or request parameter) specifies a variant,
2193 // the content may have been converted to the user's language variant.
2194 $pageLang = $this->getLanguage();
2195 } else {
2196 wfDebug( __METHOD__ . ' has to guess ParserOutput language' );
2197 // Guess what Article::getParserOutput and ParserOptions::optionsHash() would decide
2198 // on a page view:
2199 //
2200 // - Pages may have a custom page_lang set in the database,
2201 // via Title::getPageLanguage/Title::getDbPageLanguage
2202 //
2203 // - Interface messages (NS_MEDIAWIKI) render based on their subpage,
2204 // via Title::getPageLanguage/ContentHandler::getPageLanguage/MessageCache::figureMessage
2205 //
2206 // - Otherwise, pages are assumed to be in the wiki's default content language.
2207 // via Title::getPageLanguage/ContentHandler::getPageLanguage/MediaWikiServices::getContentLanguage
2208 $pageLang = $title->getPageLanguage();
2209 }
2210 if ( $title->getNamespace() !== NS_MEDIAWIKI ) {
2211 $services = MediaWikiServices::getInstance();
2212 $langConv = $services->getLanguageConverterFactory()->getLanguageConverter( $pageLang );
2213 // NOTE: LanguageConverter::getPreferredVariant inspects global RequestContext.
2214 // This usually returns $pageLang unchanged.
2215 $variant = $langConv->getPreferredVariant();
2216 if ( $pageLang->getCode() !== $variant ) {
2217 $pageLang = $services->getLanguageFactory()->getLanguage( $variant );
2218 }
2219 }
2220 $this->contentLang = $pageLang;
2221 }
2222 return $this->contentLang;
2223 }
2224
2233 public function addParserOutputMetadata( ParserOutput $parserOutput ) {
2234 // T301020 This should eventually use the standard "merge ParserOutput"
2235 // function between $parserOutput and $this->metadata.
2236 foreach (
2237 $parserOutput->getLinkList( ParserOutputLinkTypes::LANGUAGE )
2238 as $linkItem
2239 ) {
2240 $this->metadata->appendLinkList( ParserOutputLinkTypes::LANGUAGE, $linkItem );
2241 }
2242
2243 $cats = [];
2244 foreach (
2245 $parserOutput->getLinkList( ParserOutputLinkTypes::CATEGORY )
2246 as [ 'link' => $link, 'sort' => $sort ]
2247 ) {
2248 $cats[$link->getDBkey()] = $sort;
2249 }
2250 $this->addCategoryLinks( $cats );
2251
2252 // Parser-generated indicators get wrapped like other parser output.
2253 $wrapClass = $parserOutput->getWrapperDivClass();
2254 $result = [];
2255 foreach ( $parserOutput->getIndicators() as $name => $html ) {
2256 if ( $html !== '' && $wrapClass !== '' ) {
2257 $html = Html::rawElement( 'div', [ 'class' => $wrapClass ], $html );
2258 }
2259 $result[$name] = $html;
2260 }
2261 $this->setIndicators( $result );
2262
2263 $tocData = $parserOutput->getTOCData();
2264 // Do not override existing TOC data if the new one is empty (T307256#8817705)
2265 // TODO: Invent a way to merge TOCs from multiple outputs (T327429)
2266 if ( $tocData !== null && ( $this->tocData === null || count( $tocData->getSections() ) > 0 ) ) {
2267 $this->setTOCData( $tocData );
2268 }
2269
2270 if ( !$parserOutput->isCacheable() ) {
2271 $this->disableClientCache();
2272 }
2273 $this->addHeadItems( $parserOutput->getHeadItems() );
2274 $this->addModules( $parserOutput->getModules() );
2275 $this->addModuleStyles( $parserOutput->getModuleStyles() );
2276 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
2277 if ( $parserOutput->getPreventClickjacking() ) {
2278 $this->metadata->setPreventClickjacking( true );
2279 }
2280 $scriptSrcs = $parserOutput->getExtraCSPScriptSrcs();
2281 foreach ( $scriptSrcs as $src ) {
2282 $this->getCSP()->addScriptSrc( $src );
2283 }
2284 $defaultSrcs = $parserOutput->getExtraCSPDefaultSrcs();
2285 foreach ( $defaultSrcs as $src ) {
2286 $this->getCSP()->addDefaultSrc( $src );
2287 }
2288 $styleSrcs = $parserOutput->getExtraCSPStyleSrcs();
2289 foreach ( $styleSrcs as $src ) {
2290 $this->getCSP()->addStyleSrc( $src );
2291 }
2292
2293 // If $wgImagePreconnect is true, and if the output contains images, give the user-agent
2294 // a hint about a remote hosts from which images may be served. Launched in T123582.
2295 if ( $this->getConfig()->get( MainConfigNames::ImagePreconnect ) && $parserOutput->hasImages() ) {
2296 $preconnect = [];
2297 // Optimization: Instead of processing each image, assume that wikis either serve both
2298 // foreign and local from the same remote hostname (e.g. public wikis at WMF), or that
2299 // foreign images are common enough to be worth the preconnect (e.g. private wikis).
2300 $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
2301 $repoGroup->forEachForeignRepo( static function ( $repo ) use ( &$preconnect ) {
2302 $preconnect[] = $repo->getZoneUrl( 'thumb' );
2303 } );
2304 // Consider both foreign and local repos. While LocalRepo by default uses a relative
2305 // path on the same domain, wiki farms may configure it to use a dedicated hostname.
2306 $preconnect[] = $repoGroup->getLocalRepo()->getZoneUrl( 'thumb' );
2307 foreach ( $preconnect as $url ) {
2308 $host = parse_url( $url, PHP_URL_HOST );
2309 // It is expected that file URLs are often path-only, without hostname (T317329).
2310 if ( $host ) {
2311 $this->addLink( [ 'rel' => 'preconnect', 'href' => '//' . $host ] );
2312 break;
2313 }
2314 }
2315 }
2316
2317 // Template versioning and File Search Options
2318 foreach ( [
2319 ParserOutputLinkTypes::TEMPLATE,
2320 ParserOutputLinkTypes::MEDIA,
2321 ] as $linkType ) {
2322 foreach ( $parserOutput->getLinkList( $linkType ) as $linkItem ) {
2323 $this->metadata->appendLinkList( $linkType, $linkItem );
2324 }
2325 }
2326
2327 // Enable OOUI if requested via ParserOutput
2328 if ( $parserOutput->getEnableOOUI() ) {
2329 $this->enableOOUI();
2330 }
2331
2332 // Include parser limit report
2333 // FIXME: This should append, rather than overwrite, or else this
2334 // data should be injected into the OutputPage like is done for the
2335 // other page-level things (like OutputPage::setTOCData()).
2336 if ( !$this->limitReportJSData ) {
2337 $this->limitReportJSData = $parserOutput->getLimitReportJSData();
2338 }
2339
2340 // Link flags are ignored for now, but may in the future be
2341 // used to mark individual language links.
2342 $linkFlags = [];
2343 $languageLinks = $this->getLanguageLinks();
2344 sort( $languageLinks );
2345 // This hook can be used to remove/replace language links
2346 $this->getHookRunner()->onLanguageLinks( $this->getTitle(), $languageLinks, $linkFlags );
2347 $this->metadata->clearLanguageLinks();
2348 foreach ( ( $languageLinks ?? [] ) as $l ) {
2349 $this->metadata->addLanguageLink( $l );
2350 }
2351
2352 $this->getHookRunner()->onOutputPageParserOutput( $this, $parserOutput );
2353
2354 // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
2355 // so that extensions may modify ParserOutput to toggle TOC.
2356 // This cannot be moved to addParserOutputText because that is not
2357 // called by EditPage for Preview.
2358
2359 // ParserOutputFlags::SHOW_TOC is used to indicate whether the TOC
2360 // should be shown (or hidden) in the output.
2361 $this->mEnableTOC = $this->mEnableTOC ||
2362 $parserOutput->getOutputFlag( ParserOutputFlags::SHOW_TOC );
2363 // Uniform handling of all boolean flags: they are OR'ed together
2364 // (See ParserOutput::collectMetadata())
2365 $flags =
2366 array_flip( $parserOutput->getAllFlags() ) +
2367 array_flip( ParserOutputFlags::values() );
2368 foreach ( $flags as $name => $ignore ) {
2369 if ( $parserOutput->getOutputFlag( $name ) ) {
2370 $this->metadata->setOutputFlag( $name );
2371 }
2372 }
2373 }
2374
2375 private function getParserOutputText(
2376 ParserOutput $parserOutput,
2377 ParserOptions $parserOptions,
2378 array $poOptions
2379 ): string {
2380 // Add default options from the skin
2381 $skin = $this->getSkin();
2382 $skinOptions = $skin->getOptions();
2383 $oldText = $parserOutput->getContentHolderText();
2384 $poOptions += [
2385 // T371022
2386 'allowClone' => false,
2387 'skin' => $skin,
2388 'injectTOC' => $skinOptions['toc'],
2389 ];
2390 $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
2391 // Note: this path absolutely expects the metadata of $parserOutput to be mutated by the pipeline,
2392 // but the raw text should not be, see T353257
2393 // TODO T371008 consider if using the Content framework makes sense instead of creating the pipeline
2394 $text = $pipeline->run(
2395 $parserOutput,
2396 // This should be the same parser options that generated
2397 // $parserOutput
2398 $parserOptions,
2399 $poOptions
2400 )->getContentHolderText();
2401 $parserOutput->setContentHolderText( $oldText );
2402 return $text;
2403 }
2404
2415 public function addParserOutputContent(
2416 ParserOutput $parserOutput,
2417 ParserOptions $parserOptions,
2418 ?array $poOptions = null,
2419 ) {
2420 $poOptions ??= [];
2421 $text = $this->getParserOutputText( $parserOutput, $parserOptions, $poOptions );
2422 $this->addParserOutputText( $text, $poOptions );
2423
2424 $this->addModules( $parserOutput->getModules() );
2425 $this->addModuleStyles( $parserOutput->getModuleStyles() );
2426
2427 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
2428 }
2429
2437 public function addParserOutputText( string $text, $poOptions = [] ) {
2438 $this->getHookRunner()->onOutputPageBeforeHTML( $this, $text );
2439 $this->addHTML( $text );
2440 }
2441
2449 public function addParserOutput(
2450 ParserOutput $parserOutput,
2451 ParserOptions $parserOptions,
2452 ?array $poOptions = null,
2453 ) {
2454 $poOptions ??= [];
2455
2457 $text = $this->getParserOutputText( $parserOutput, $parserOptions, $poOptions );
2458 $this->addParserOutputMetadata( $parserOutput );
2459 $this->addParserOutputText( $text, $poOptions );
2460 }
2461
2462 public function addPostProcessedParserOutput( ParserOutput $parserOutput ) {
2463 $this->addParserOutputMetadata( $parserOutput );
2464 $this->addParserOutputText( $parserOutput->getContentHolderText() );
2465 }
2466
2472 public function addTemplate( &$template ) {
2473 $this->addHTML( $template->getHTML() );
2474 }
2475
2486 public function parseAsContent( $text, $linestart = true ) {
2487 $title = $this->getTitle();
2488 if ( $title === null ) {
2489 throw new RuntimeException( 'No title in ' . __METHOD__ );
2490 }
2491 [ $po, ] = $this->parseInternal(
2492 $text, $title, $linestart,
2493 $this->internalParserOptions( false ),
2494 /*allowTOC*/ false, /*wrapperDivClass*/ null, /*postprocess*/ true
2495 );
2496 return $po->getContentHolderText();
2497 }
2498
2510 public function parseAsInterface( $text, $linestart = true ) {
2511 $title = $this->getTitle();
2512 if ( $title === null ) {
2513 throw new RuntimeException( 'No title in ' . __METHOD__ );
2514 }
2515 [ $po, ] = $this->parseInternal(
2516 $text, $title, $linestart,
2517 $this->internalParserOptions( true ),
2518 /*allowTOC*/ false, /*wrapperDivClass*/ null, /*postprocess*/ true
2519 );
2520 return $po->getContentHolderText();
2521 }
2522
2536 public function parseInlineAsInterface( $text, $linestart = true ) {
2537 return Parser::stripOuterParagraph(
2538 $this->parseAsInterface( $text, $linestart )
2539 );
2540 }
2541
2554 private function parseInternal(
2555 string $text, PageReference $title,
2556 bool $linestart, ParserOptions $popts, bool $allowTOC, ?string $wrapperClass,
2557 bool $postprocess
2558 ) {
2559 $parserOutput = MediaWikiServices::getInstance()->getParserFactory()->getInstance()
2560 ->parse(
2561 $text, $title, $popts,
2562 $linestart, true, $this->mRevisionId
2563 );
2564
2565 // Don't include default mw-parser-output wrap class, just use our own
2566 $parserOutput->clearWrapperDivClass();
2567 if ( $wrapperClass !== null ) {
2568 $parserOutput->addWrapperDivClass( $wrapperClass );
2569 }
2570
2571 if ( !$allowTOC ) {
2572 $parserOutput->setOutputFlag( ParserOutputFlags::NO_TOC );
2573 $parserOutput->setSections( [] );
2574 }
2575
2576 if ( $postprocess ) {
2577 $pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
2578 // TODO T371008 consider if using the Content framework makes sense instead of creating the pipeline
2579 $parserOutput = $pipeline->run(
2580 $parserOutput, $popts, [
2581 'userLang' => $this->getContext()->getLanguage(),
2582 ]
2583 );
2584 }
2585
2586 return [ $parserOutput, $popts ];
2587 }
2588
2594 public function setCdnMaxage( $maxage ) {
2595 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2596 }
2597
2607 public function lowerCdnMaxage( $maxage ) {
2608 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2609 $this->setCdnMaxage( $this->mCdnMaxage );
2610 }
2611
2624 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2625 $minTTL = $minTTL ?: 60;
2626 $maxTTL = $maxTTL ?: $this->getConfig()->get( MainConfigNames::CdnMaxAge );
2627
2628 if ( $mtime === null || $mtime === false ) {
2629 // entity does not exist
2630 return;
2631 }
2632
2633 $age = MWTimestamp::time() - (int)wfTimestamp( TS::UNIX, $mtime );
2634 $adaptiveTTL = max( 0.9 * $age, $minTTL );
2635 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2636
2637 $this->lowerCdnMaxage( (int)$adaptiveTTL );
2638 }
2639
2643 public function enableClientCache(): void {
2644 $this->mEnableClientCache = true;
2645 }
2646
2651 public function disableClientCache(): void {
2652 $this->mEnableClientCache = false;
2653 }
2654
2661 public function couldBePublicCached() {
2662 if ( !$this->cacheIsFinal ) {
2663 // - The entry point handles its own caching and/or doesn't use OutputPage.
2664 // (such as load.php, or MediaWiki\Rest\EntryPoint).
2665 //
2666 // - Or, we haven't finished processing the main part of the request yet
2667 // (e.g. Action::show, SpecialPage::execute), and the state may still
2668 // change via enableClientCache().
2669 return true;
2670 }
2671 // e.g. various error-type pages disable all client caching
2672 return $this->mEnableClientCache;
2673 }
2674
2684 public function considerCacheSettingsFinal() {
2685 $this->cacheIsFinal = true;
2686 }
2687
2688 private function getSessionManager(): SessionManagerInterface {
2689 return MediaWikiServices::getInstance()->getSessionManager();
2690 }
2691
2697 public function getCacheVaryCookies() {
2698 if ( self::$cacheVaryCookies === null ) {
2699 $config = $this->getConfig();
2700 self::$cacheVaryCookies = array_values( array_unique( array_merge(
2701 $this->getSessionManager()->getVaryCookies(),
2702 [
2703 'forceHTTPS',
2704 ],
2705 $config->get( MainConfigNames::CacheVaryCookies )
2706 ) ) );
2707 $this->getHookRunner()->onGetCacheVaryCookies( $this, self::$cacheVaryCookies );
2708 }
2709 return self::$cacheVaryCookies;
2710 }
2711
2718 public function haveCacheVaryCookies() {
2719 $request = $this->getRequest();
2720 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2721 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2722 wfDebug( __METHOD__ . ": found $cookieName" );
2723 return true;
2724 }
2725 }
2726 wfDebug( __METHOD__ . ': no cache-varying cookies found' );
2727 return false;
2728 }
2729
2735 public function addVaryHeader( $header ) {
2736 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2737 $this->mVaryHeader[$header] = null;
2738 }
2739 }
2740
2747 public function getVaryHeader() {
2748 // If we vary on cookies, let's make sure it's always included here too.
2749 if ( $this->getCacheVaryCookies() ) {
2750 $this->addVaryHeader( 'Cookie' );
2751 }
2752
2753 foreach ( $this->getSessionManager()->getVaryHeaders() as $header => $_ ) {
2754 $this->addVaryHeader( $header );
2755 }
2756 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2757 }
2758
2764 public function addLinkHeader( $header ) {
2765 $this->mLinkHeader[] = $header;
2766 }
2767
2773 public function getLinkHeader() {
2774 if ( !$this->mLinkHeader ) {
2775 return false;
2776 }
2777
2778 return 'Link: ' . implode( ',', $this->mLinkHeader );
2779 }
2780
2788 private function addAcceptLanguage() {
2789 $title = $this->getTitle();
2790 if ( !$title instanceof Title ) {
2791 return;
2792 }
2793
2794 $languageConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
2795 ->getLanguageConverter( $title->getPageLanguage() );
2796 if ( !$this->getRequest()->getCheck( 'variant' ) && $languageConverter->hasVariants() ) {
2797 $this->addVaryHeader( 'Accept-Language' );
2798 }
2799 }
2800
2820 public function setPreventClickjacking( bool $enable ) {
2821 $this->metadata->setPreventClickjacking( $enable );
2822 }
2823
2831 public function getPreventClickjacking() {
2832 return $this->metadata->getPreventClickjacking();
2833 }
2834
2842 public function getFrameOptions() {
2843 $config = $this->getConfig();
2844 if ( $config->get( MainConfigNames::BreakFrames ) ) {
2845 return 'DENY';
2846 } elseif (
2847 $this->metadata->getPreventClickjacking() &&
2848 $config->get( MainConfigNames::EditPageFrameOptions )
2849 ) {
2850 return $config->get( MainConfigNames::EditPageFrameOptions );
2851 }
2852 return false;
2853 }
2854
2856 private function getReportTo() {
2857 $config = $this->getConfig();
2858
2859 $expiry = $config->get( MainConfigNames::ReportToExpiry );
2860
2861 if ( !$expiry ) {
2862 return false;
2863 }
2864
2865 $endpoints = $config->get( MainConfigNames::ReportToEndpoints );
2866
2867 if ( !$endpoints ) {
2868 return false;
2869 }
2870
2871 $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
2872
2873 foreach ( $endpoints as $endpoint ) {
2874 $output['endpoints'][] = [ 'url' => $endpoint ];
2875 }
2876
2877 return json_encode( $output, JSON_UNESCAPED_SLASHES );
2878 }
2879
2880 private function getFeaturePolicyReportOnly(): string {
2881 $config = $this->getConfig();
2882
2883 $features = $config->get( MainConfigNames::FeaturePolicyReportOnly );
2884 return implode( ';', $features );
2885 }
2886
2890 public function sendCacheControl() {
2891 $response = $this->getRequest()->response();
2892 $config = $this->getConfig();
2893
2894 $this->addVaryHeader( 'Cookie' );
2895 $this->addAcceptLanguage();
2896
2897 # don't serve compressed data to clients who can't handle it
2898 # maintain different caches for logged-in users and non-logged in ones
2899 $response->header( $this->getVaryHeader() );
2900
2901 if ( $this->mEnableClientCache ) {
2902 if ( !$config->get( MainConfigNames::UseCdn ) ) {
2903 $privateReason = 'config';
2904 } elseif ( $response->hasCookies() ) {
2905 $privateReason = 'set-cookies';
2906 // The client might use methods other than cookies to appear logged-in.
2907 // E.g. HTTP headers, or query parameter tokens, OAuth, etc.
2908 } elseif ( $this->getRequest()->getSession()->isPersistent() ) {
2909 $privateReason = 'session';
2910 } elseif ( $this->mCdnMaxage == 0 ) {
2911 $privateReason = 'no-maxage';
2912 } elseif ( $this->haveCacheVaryCookies() ) {
2913 $privateReason = 'cache-vary-cookies';
2914 } else {
2915 $privateReason = false;
2916 }
2917
2918 if ( $privateReason === false ) {
2919 # We'll purge the proxy cache for anons explicitly, but require end user agents
2920 # to revalidate against the proxy on each visit.
2921 # IMPORTANT! The CDN needs to replace the Cache-Control header with
2922 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2923 wfDebug( __METHOD__ .
2924 ": local proxy caching; {$this->mLastModified} **", 'private' );
2925 # start with a shorter timeout for initial testing
2926 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2927 $response->header( 'Cache-Control: ' .
2928 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2929 } else {
2930 # We do want clients to cache if they can, but they *must* check for updates
2931 # on revisiting the page.
2932 wfDebug( __METHOD__ . ": private caching ($privateReason); {$this->mLastModified} **", 'private' );
2933
2934 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2935 $response->header( 'Cache-Control: private, must-revalidate, max-age=0' );
2936 }
2937 if ( $this->mLastModified ) {
2938 $response->header( "Last-Modified: {$this->mLastModified}" );
2939 }
2940 } else {
2941 wfDebug( __METHOD__ . ': no caching **', 'private' );
2942
2943 # In general, the absence of a last modified header should be enough to prevent
2944 # the client from using its cache. We send a few other things just to make sure.
2945 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2946 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2947 }
2948 }
2949
2955 public function loadSkinModules( $sk ) {
2956 foreach ( $sk->getDefaultModules() as $group => $modules ) {
2957 if ( $group === 'styles' ) {
2958 foreach ( $modules as $moduleMembers ) {
2959 $this->addModuleStyles( $moduleMembers );
2960 }
2961 } else {
2962 $this->addModules( $modules );
2963 }
2964 }
2965 }
2966
2974 public function output( $return = false ) {
2975 if ( $this->mDoNothing ) {
2976 return $return ? '' : null;
2977 }
2978
2979 $request = $this->getRequest();
2980 $response = $request->response();
2981 $config = $this->getConfig();
2982
2983 if ( $this->mRedirect != '' ) {
2984 $services = MediaWikiServices::getInstance();
2985 // We do not expand redirect destinations to a full URL, because:
2986 // * Relative URLs are widely supported and valid under the HTTP 1.1 spec (RFC 7131).
2987 // * Expanding a absolute-path URL like "/wiki/Foo" can cause surprising cross-domain
2988 // redirects (T406402).
2989 // * Expanding a relative-path URL like "../Foo" using UrlUtils::expand would corrupt
2990 // the path instead of resolving against the current document location.
2991 // * Expanding a protocol-relative URL like "//example.org/Foo" would compromise
2992 // cacheability of the redirect response.
2993
2994 $redirect = $this->mRedirect;
2995 $code = $this->mRedirectCode;
2996 $content = '';
2997
2998 if ( $this->getHookRunner()->onBeforePageRedirect( $this, $redirect, $code ) ) {
2999 if ( $code == '301' || $code == '303' ) {
3000 if ( !$config->get( MainConfigNames::DebugRedirects ) ) {
3001 $response->statusHeader( (int)$code );
3002 }
3003 $this->mLastModified = wfTimestamp( TS::RFC2822 );
3004 }
3005 if ( $config->get( MainConfigNames::VaryOnXFP ) ) {
3006 $this->addVaryHeader( 'X-Forwarded-Proto' );
3007 }
3008 $this->sendCacheControl();
3009
3010 $response->header( 'Content-Type: text/html; charset=UTF-8' );
3011 if ( $config->get( MainConfigNames::DebugRedirects ) ) {
3012 $url = htmlspecialchars( $redirect );
3013 $content = "<!DOCTYPE html>\n<html>\n<head>\n"
3014 . "<title>Redirect</title>\n</head>\n<body>\n"
3015 . "<p>Location: <a href=\"$url\">$url</a></p>\n"
3016 . "</body>\n</html>\n";
3017
3018 if ( !$return ) {
3019 print $content;
3020 }
3021
3022 } else {
3023 $response->header( 'Location: ' . $redirect );
3024 }
3025 }
3026
3027 return $return ? $content : null;
3028 } elseif ( $this->mStatusCode ) {
3029 $response->statusHeader( $this->mStatusCode );
3030 }
3031
3032 # Buffer output; final headers may depend on later processing
3033 ob_start();
3034
3035 $response->header( 'Content-language: ' .
3036 MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
3037
3038 $linkHeader = $this->getLinkHeader();
3039 if ( $linkHeader ) {
3040 $response->header( $linkHeader );
3041 }
3042
3043 // Prevent framing, if requested
3044 $frameOptions = $this->getFrameOptions();
3045 if ( $frameOptions ) {
3046 $response->header( "X-Frame-Options: $frameOptions" );
3047 }
3048
3049 // Get the Origin-Trial header values. This is used to enable Chrome Origin
3050 // Trials: https://github.com/GoogleChrome/OriginTrials
3051 $originTrials = $config->get( MainConfigNames::OriginTrials );
3052 foreach ( $originTrials as $originTrial ) {
3053 $response->header( "Origin-Trial: $originTrial", false );
3054 }
3055
3056 $reportTo = $this->getReportTo();
3057 if ( $reportTo ) {
3058 $response->header( "Report-To: $reportTo" );
3059 }
3060
3061 $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
3062 if ( $featurePolicyReportOnly ) {
3063 $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
3064 }
3065
3066 if ( $this->mArticleBodyOnly ) {
3067 $response->header( 'Content-type: ' . $config->get( MainConfigNames::MimeType ) . '; charset=UTF-8' );
3068 if ( $this->cspOutputMode === self::CSP_HEADERS ) {
3069 $this->CSP->sendHeaders();
3070 }
3071 echo $this->mBodytext;
3072 } else {
3073 // Enable safe mode if requested (T152169)
3074 if ( $this->getRequest()->getBool( 'safemode' ) ) {
3075 $this->disallowUserJs();
3076 }
3077
3078 $sk = $this->getSkin();
3079 $skinOptions = $sk->getOptions();
3080
3081 if ( $skinOptions['format'] === 'json' ) {
3082 $response->header( 'Content-type: application/json; charset=UTF-8' );
3083 return json_encode( [
3084 '@WARNING' => $this->msg( 'skin-json-warning-message' )->escaped()
3085 ] + $sk->getTemplateData() );
3086 }
3087 $response->header( 'Content-type: ' . $config->get( MainConfigNames::MimeType ) . '; charset=UTF-8' );
3088 $this->loadSkinModules( $sk );
3089
3090 MWDebug::addModules( $this );
3091
3092 // Hook that allows last minute changes to the output page, e.g.
3093 // adding of CSS or JavaScript by extensions, adding CSP sources.
3094 $this->getHookRunner()->onBeforePageDisplay( $this, $sk );
3095
3096 if ( $this->cspOutputMode === self::CSP_HEADERS ) {
3097 $this->CSP->sendHeaders();
3098 }
3099
3100 try {
3101 $sk->outputPageFinal( $this );
3102 } catch ( Exception $e ) {
3103 ob_end_clean(); // bug T129657
3104 throw $e;
3105 }
3106 }
3107
3108 try {
3109 // This hook allows last minute changes to final overall output by modifying output buffer
3110 $this->getHookRunner()->onAfterFinalPageOutput( $this );
3111 } catch ( Exception $e ) {
3112 ob_end_clean(); // bug T129657
3113 throw $e;
3114 }
3115
3116 $this->sendCacheControl();
3117
3118 if ( $return ) {
3119 return ob_get_clean();
3120 } else {
3121 ob_end_flush();
3122 return null;
3123 }
3124 }
3125
3132 public function prepareErrorPage() {
3133 $this->setRobotPolicy( 'noindex,nofollow' );
3134 $this->setArticleRelated( false );
3135 $this->disableClientCache();
3136 $this->mRedirect = '';
3137 $this->clearSubtitle();
3138 $this->clearHTML();
3139 }
3140
3157 public function showErrorPage(
3158 $title, $msg, $params = [], $returnto = null, $returntoquery = null
3159 ) {
3160 if ( !$title instanceof Message ) {
3161 $title = $this->msg( $title );
3162 }
3163
3164 $this->prepareErrorPage();
3165 $this->setPageTitleMsg( $title );
3166
3167 if ( $msg instanceof Message ) {
3168 if ( $params !== [] ) {
3169 trigger_error( 'Argument ignored: $params. The message parameters argument '
3170 . 'is discarded when the $msg argument is a Message object instead of '
3171 . 'a string.', E_USER_NOTICE );
3172 }
3173 $this->addHTML( $msg->parseAsBlock() );
3174 } else {
3175 $this->addWikiMsgArray( $msg, $params );
3176 }
3177
3178 $this->addJsConfigVars( 'wgErrorPageMessageKey', is_string( $msg ) ? $msg : $msg->getKey() );
3179
3180 $this->returnToMain( null, $returnto, $returntoquery );
3181 }
3182
3189 public function showPermissionStatus( PermissionStatus $status, $action = null ) {
3190 Assert::precondition( !$status->isGood(), 'Status must have errors' );
3191
3192 $messages = $status->getMessages();
3193
3194 $services = MediaWikiServices::getInstance();
3195 $groupPermissionsLookup = $services->getGroupPermissionsLookup();
3196
3197 // Display a "login to do this action" error if all of the following conditions are met:
3198 // 1. the user is not logged in as a named user, and so cannot be added to groups
3199 // 2. the only error is insufficient permissions (i.e. no block or something else)
3200 // 3. the error can be avoided simply by logging in
3201
3202 if ( $action !== null && !$this->getUser()->isNamed() && count( $messages ) == 1
3203 && ( $messages[0]->getKey() == 'badaccess-groups' || $messages[0]->getKey() == 'badaccess-group0' )
3204 && ( $groupPermissionsLookup->groupHasPermission( 'user', $action )
3205 || $groupPermissionsLookup->groupHasPermission( 'autoconfirmed', $action ) )
3206 ) {
3207 $displayReturnto = null;
3208
3209 # Due to T34276, if a user does not have read permissions,
3210 # $this->getTitle() will just give Special:Badtitle, which is
3211 # not especially useful as a returnto parameter. Use the title
3212 # from the request instead, if there was one.
3213 $request = $this->getRequest();
3214 $returnto = Title::newFromText( $request->getText( 'title' ) );
3215 $extraParams = [];
3216 if ( $action == 'edit' ) {
3217 $msg = 'whitelistedittext';
3218 $displayReturnto = $returnto;
3219 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
3220 $msg = 'nocreatetext';
3221 } elseif ( $action == 'upload' ) {
3222 $msg = 'uploadnologintext';
3223 } elseif ( $action === 'read' ) {
3224 $msg = 'loginreqpagetext';
3225 $displayReturnto = Title::newMainPage();
3226 } else {
3227 $msg = 'permissionerror-login';
3228 $action_desc = $this->msg( "action-$action" )->plain();
3229 $extraParams = [ $action_desc ];
3230 }
3231
3232 $query = [];
3233
3234 if ( $returnto ) {
3235 $query['returnto'] = $returnto->getPrefixedText();
3236
3237 if ( !$request->wasPosted() ) {
3238 $returntoquery = $request->getQueryValues();
3239 unset( $returntoquery['title'] );
3240 unset( $returntoquery['returnto'] );
3241 unset( $returntoquery['returntoquery'] );
3242 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
3243 }
3244 }
3245
3246 $title = SpecialPage::getTitleFor( 'Userlogin' );
3247 $linkRenderer = $services->getLinkRenderer();
3248 $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
3249 $loginLink = $linkRenderer->makeKnownLink(
3250 $title,
3251 $this->msg( 'loginreqlink' )->text(),
3252 [],
3253 $query
3254 );
3255
3256 $this->prepareErrorPage();
3257 $this->setPageTitleMsg( $this->msg( 'loginreqtitle' ) );
3258 $this->addHTML( $this->msg( $msg )
3259 ->rawParams( $loginLink )
3260 ->params( $loginUrl )
3261 ->params( $extraParams )
3262 ->parse()
3263 );
3264
3265 # Don't return to a page the user can't read otherwise
3266 # we'll end up in a pointless loop
3267 if ( $displayReturnto && $this->getAuthority()->probablyCan( 'read', $displayReturnto ) ) {
3268 $this->returnToMain( null, $displayReturnto );
3269 }
3270 } else {
3271 $this->prepareErrorPage();
3272 $this->setPageTitleMsg( $this->msg( 'permissionserrors' ) );
3273 $this->addWikiTextAsInterface( $this->formatPermissionStatus( $status, $action ) );
3274 }
3275 }
3276
3283 public function versionRequired( $version ) {
3284 $this->prepareErrorPage();
3285 $this->setPageTitleMsg(
3286 $this->msg( 'versionrequired' )->plaintextParams( $version )
3287 );
3288
3289 $this->addWikiMsg( 'versionrequiredtext', $version );
3290 $this->returnToMain();
3291 }
3292
3304 public function formatPermissionStatus( PermissionStatus $status, ?string $action = null ): string {
3305 if ( $status->isGood() ) {
3306 return '';
3307 }
3308
3309 if ( !$status->hasMessagesExcept( 'badaccess-group0' ) ) {
3310 // We don't know why you can't do it; admit that rather than saying the circular
3311 // "you don't have permission to do this because you are not allowed to do this"
3312 if ( $action === null ) {
3313 // We don't know what you were trying to do either.
3314 // At least say just "You are not allowed to do that" once rather than twice
3315 $text = $this->msg( 'badaccess-group0' )->plain();
3316 } else {
3317 $action_desc = $this->msg( "action-$action" )->plain();
3318 $text = $this->msg( 'permissionserrorstext-withaction-noreason', $action_desc )->plain();
3319 }
3320 return Html::rawElement( 'div', [ 'class' => 'permissions-errors' ], $text );
3321 }
3322
3323 $messages = array_map( $this->msg( ... ), $status->getMessages() );
3324
3325 if ( $action == null ) {
3326 $text = $this->msg( 'permissionserrorstext', count( $messages ) )->plain() . "\n\n";
3327 } else {
3328 $action_desc = $this->msg( "action-$action" )->plain();
3329 $text = $this->msg(
3330 'permissionserrorstext-withaction',
3331 count( $messages ),
3332 $action_desc
3333 )->plain() . "\n\n";
3334 }
3335
3336 if ( count( $messages ) > 1 ) {
3337 $text .= Html::openElement( 'ul', [ 'class' => 'permissions-errors' ] );
3338 foreach ( $messages as $message ) {
3339 $text .= Html::rawElement(
3340 'li',
3341 [ 'class' => 'mw-permissionerror-' . $message->getKey() ],
3342 $message->plain()
3343 );
3344 }
3345 $text .= Html::closeElement( 'ul' );
3346 } else {
3347 $text .= Html::openElement( 'div', [ 'class' => 'permissions-errors' ] );
3348 $text .= Html::rawElement(
3349 'div',
3350 [ 'class' => 'mw-permissionerror-' . $messages[ 0 ]->getKey() ],
3351 $messages[ 0 ]->plain()
3352 );
3353 $text .= Html::closeElement( 'div' );
3354 }
3355
3356 return $text;
3357 }
3358
3368 public function showLagWarning( $lag ) {
3369 $config = $this->getConfig();
3370 if ( $lag >= $config->get( MainConfigNames::DatabaseReplicaLagWarning ) ) {
3371 // floor to avoid nano seconds to display
3372 $lag = floor( $lag );
3373 $message = $lag < $config->get( MainConfigNames::DatabaseReplicaLagCritical )
3374 ? 'lag-warn-normal'
3375 : 'lag-warn-high';
3376 // For grep: mw-lag-warn-normal, mw-lag-warn-high
3377 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
3378 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
3379 }
3380 }
3381
3390 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
3391 $linkRenderer = MediaWikiServices::getInstance()
3392 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
3393 $link = $this->msg( 'returnto' )->rawParams(
3394 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
3395 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
3396 }
3397
3406 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
3407 $returnto ??= $this->getRequest()->getText( 'returnto' );
3408
3409 $returntoquery ??= $this->getRequest()->getText( 'returntoquery' );
3410
3411 if ( $returnto === '' ) {
3412 $returnto = Title::newMainPage();
3413 }
3414
3415 if ( is_object( $returnto ) ) {
3416 $linkTarget = TitleValue::castPageToLinkTarget( $returnto );
3417 } else {
3418 $linkTarget = Title::newFromText( $returnto );
3419 }
3420
3421 // We don't want people to return to external interwiki. That
3422 // might potentially be used as part of a phishing scheme
3423 if ( !$linkTarget || $linkTarget->isExternal() ) {
3424 $linkTarget = Title::newMainPage();
3425 }
3426
3427 $this->addReturnTo( $linkTarget, wfCgiToArray( $returntoquery ) );
3428 }
3429
3448 public function showPendingTakeover(
3449 $fallbackUrl, $msg, ...$params
3450 ) {
3451 if ( $msg instanceof Message ) {
3452 if ( $params !== [] ) {
3453 trigger_error( 'Argument ignored: $params. The message parameters argument '
3454 . 'is discarded when the $msg argument is a Message object instead of '
3455 . 'a string.', E_USER_NOTICE );
3456 }
3457 $this->addHTML( $msg->parseAsBlock() );
3458 } else {
3459 $this->addHTML( $this->msg( $msg, ...$params )->parseAsBlock() );
3460 }
3461
3462 // Redirect if the user has no JS (<noscript>)
3463 $escapedUrl = htmlspecialchars( $fallbackUrl );
3464 $this->addHeadItem(
3465 'mw-noscript-fallback',
3466 // https://html.spec.whatwg.org/#attr-meta-http-equiv-refresh
3467 // means that if $fallbackUrl contains unencoded quotation marks
3468 // then this will behave confusingly, but shouldn't break the page
3469 "<noscript><meta http-equiv=\"refresh\" content=\"0; url=$escapedUrl\"></noscript>"
3470 );
3471 // Redirect if the user has no ResourceLoader
3472 $this->addScript( Html::inlineScript(
3473 '(window.NORLQ=window.NORLQ||[]).push(' .
3474 'function(){' .
3475 'location.href=' . json_encode( $fallbackUrl ) . ';' .
3476 '}' .
3477 ');'
3478 ) );
3479 }
3480
3491 private function inDebugMode() {
3492 if ( $this->debugMode === null ) {
3493 $resourceLoaderDebug = $this->getConfig()->get(
3494 MainConfigNames::ResourceLoaderDebug );
3495 $str = $this->getRequest()->getRawVal( 'debug' ) ??
3496 $this->getRequest()->getCookie( 'resourceLoaderDebug', '', $resourceLoaderDebug ? 'true' : '' );
3497 $this->debugMode = RL\Context::debugFromString( $str );
3498 }
3499 return $this->debugMode;
3500 }
3501
3502 private function getRlClientContext(): RL\Context {
3503 if ( !$this->rlClientContext ) {
3504 $query = ResourceLoader::makeLoaderQuery(
3505 [], // modules; not relevant
3506 $this->getLanguage()->getCode(),
3507 $this->getSkin()->getSkinName(),
3508 $this->getUser()->isRegistered() ? $this->getUser()->getName() : null,
3509 null, // version; not relevant
3510 $this->inDebugMode(),
3511 null, // only; not relevant
3512 $this->isPrintable()
3513 );
3514 $this->rlClientContext = new RL\Context(
3515 $this->getResourceLoader(),
3516 new FauxRequest( $query )
3517 );
3518 if ( $this->contentOverrideCallbacks ) {
3519 $this->rlClientContext = new RL\DerivativeContext( $this->rlClientContext );
3520 $this->rlClientContext->setContentOverrideCallback( function ( $page ) {
3521 foreach ( $this->contentOverrideCallbacks as $callback ) {
3522 $content = $callback( $page );
3523 if ( $content !== null ) {
3524 $text = ( $content instanceof TextContent ) ? $content->getText() : '';
3525 if ( preg_match( '/<\/?script/i', $text ) ) {
3526 // Proactively replace this so that we can display a message
3527 // to the user, instead of letting it go to Html::inlineScript(),
3528 // where it would be considered a server-side issue.
3529 $content = new JavaScriptContent(
3530 Html::encodeJsCall( 'mw.log.error', [
3531 "Cannot preview $page due to suspecting script tag inside (T200506)."
3532 ] )
3533 );
3534 }
3535 return $content;
3536 }
3537 }
3538 return null;
3539 } );
3540 }
3541 }
3542 return $this->rlClientContext;
3543 }
3544
3556 public function getRlClient() {
3557 if ( !$this->rlClient ) {
3558 $context = $this->getRlClientContext();
3559 $rl = $this->getResourceLoader();
3560 $this->addModules( [
3561 'user',
3562 'user.options',
3563 ] );
3564 $this->addModuleStyles( [
3565 'site.styles',
3566 'noscript',
3567 'user.styles',
3568 ] );
3569 $generalModules = $this->getModules( /*filter*/ true );
3570 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
3571
3572 // Preload getTitleInfo for:
3573 // * $moduleStyles:
3574 // For isKnownEmpty() calls below when computing $exemptGroups,
3575 // and for isKnownEmpty() calls in RL\ClientHtml when creating stylesheet links.
3576 // * any WikiModule in $generalModules:
3577 // For isKnownEmpty() calls in RL\ClientHtml skipping empty user/embedded JS modules.
3578 $preloadBatch = $moduleStyles;
3579 foreach ( $generalModules as $name ) {
3580 if ( $rl->getModule( $name ) instanceof RL\WikiModule ) {
3581 $preloadBatch[] = $name;
3582 }
3583 }
3584 RL\WikiModule::preloadTitleInfo( $context, $preloadBatch );
3585
3586 // Filter out style modules that buildExemptModules() should handle
3587 // instead of RL\ClientHtml
3588 $exemptGroups = [
3589 RL\Module::GROUP_SITE => [],
3590 RL\Module::GROUP_NOSCRIPT => [],
3591 RL\Module::GROUP_PRIVATE => [],
3592 RL\Module::GROUP_USER => []
3593 ];
3594 $exemptStates = [];
3595 $moduleStyles = array_filter( $moduleStyles,
3596 static function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3597 $module = $rl->getModule( $name );
3598 if ( $module ) {
3599 $group = $module->getGroup();
3600 if ( $group !== null && isset( $exemptGroups[$group] ) ) {
3601 // The `noscript` module is excluded from the client
3602 // side registry, no need to set its state either.
3603 // But we still output it. See T291735
3604 if ( $group !== RL\Module::GROUP_NOSCRIPT ) {
3605 $exemptStates[$name] = 'ready';
3606 }
3607 if ( !$module->isKnownEmpty( $context ) ) {
3608 // E.g. Don't output empty <styles>
3609 $exemptGroups[$group][] = $name;
3610 }
3611 return false;
3612 }
3613 }
3614 return true;
3615 }
3616 );
3617 $this->rlExemptStyleModules = $exemptGroups;
3618
3619 $config = $this->getConfig();
3620 // Client preferences are controlled by the skin and specific to unregistered
3621 // users. See mw.user.clientPrefs for details on how this works and how to
3622 // handle registered users.
3623 $clientPrefEnabled = (
3624 $this->getSkin()->getOptions()['clientPrefEnabled'] &&
3625 !$this->getUser()->isNamed()
3626 );
3627 $clientPrefCookiePrefix = $config->get( MainConfigNames::CookiePrefix );
3628
3629 $rlClient = new RL\ClientHtml( $context, [
3630 'target' => $this->getTarget(),
3631 // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3632 // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3633 // modules enqueued for loading on this page is filtered to just those.
3634 // However, to make sure we also apply the restriction to dynamic dependencies and
3635 // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3636 // StartupModule so that the client-side registry will not contain any restricted
3637 // modules either. (T152169, T185303)
3638 'safemode' => ( $this->getAllowedModules( RL\Module::TYPE_COMBINED )
3639 <= RL\Module::ORIGIN_CORE_INDIVIDUAL
3640 ) ? '1' : null,
3641 'clientPrefEnabled' => $clientPrefEnabled,
3642 'clientPrefCookiePrefix' => $clientPrefCookiePrefix,
3643 ] );
3644 $rlClient->setConfig( $this->getJSVars( self::JS_VAR_EARLY ) );
3645 $rlClient->setModules( $generalModules );
3646 $rlClient->setModuleStyles( $moduleStyles );
3647 $rlClient->setExemptStates( $exemptStates );
3648 $this->rlClient = $rlClient;
3649 }
3650 return $this->rlClient;
3651 }
3652
3658 public function headElement( Skin $sk, $includeStyle = true ) {
3659 $config = $this->getConfig();
3660 $userdir = $this->getLanguage()->getDir();
3661 $services = MediaWikiServices::getInstance();
3662 $sitedir = $services->getContentLanguage()->getDir();
3663
3664 $rlHtmlAtribs = $this->getRlClient()->getDocumentAttributes();
3665 $skinHtmlAttribs = $sk->getHtmlElementAttributes();
3666
3667 $lookupService = $services->getUserOptionsLookup();
3668 $user = $this->getUser();
3669 $thumbnailIndex = $lookupService->getOption( $user, 'thumbsize' );
3670 $thumbnailSize = $config->get( 'ThumbLimits' )[ $thumbnailIndex ] ?? 250;
3671 $thumbValue = $thumbnailSize === 250 ? 'standard' : (
3672 $thumbnailSize < 250 ? 'small' : 'large'
3673 );
3674 // Combine the classes from different sources, and convert to a string, which is needed below
3675 $htmlClass = Html::expandClassList( [
3676 Html::expandClassList( $rlHtmlAtribs['class'] ?? [] ),
3677 Html::expandClassList( $skinHtmlAttribs['class'] ?? [] ),
3678 Html::expandClassList( $this->mAdditionalHtmlClasses ),
3679 // This uses `-clientpref-` for now to support future customization for anonymous users.
3680 'skin-thumbsize-clientpref-' . $thumbValue,
3681 ] );
3682
3683 if ( $htmlClass === '' ) {
3684 $htmlClass = null;
3685 }
3686 $htmlAttribs = array_merge( $rlHtmlAtribs, $skinHtmlAttribs, [ 'class' => $htmlClass ] );
3687
3688 $pieces = [];
3689 $pieces[] = Html::htmlHeader( $htmlAttribs );
3690 $pieces[] = Html::openElement( 'head' );
3691
3692 if ( $this->getHTMLTitle() == '' ) {
3693 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3694 }
3695
3696 if ( !Html::isXmlMimeType( $config->get( MainConfigNames::MimeType ) ) ) {
3697 // Add <meta charset="UTF-8">
3698 // This should be before <title> since it defines the charset used by
3699 // text including the text inside <title>.
3700 // The spec recommends defining XHTML5's charset using the XML declaration
3701 // instead of meta.
3702 // Our XML declaration is output by Html::htmlHeader.
3703 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3704 // https://html.spec.whatwg.org/multipage/semantics.html#charset
3705 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3706 }
3707
3708 $pieces[] = Html::element( 'title', [], $this->getHTMLTitle() );
3709 $pieces[] = $this->getRlClient()->getHeadHtml( $htmlClass );
3710 $pieces[] = $this->buildExemptModules();
3711 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3712 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3713
3714 $pieces[] = Html::closeElement( 'head' );
3715
3716 $skinOptions = $sk->getOptions();
3717 $bodyClasses = array_merge( $this->mAdditionalBodyClasses, $skinOptions['bodyClasses'] );
3718 $bodyClasses[] = 'mediawiki';
3719
3720 # Classes for LTR/RTL directionality support
3721 $bodyClasses[] = $userdir;
3722 $bodyClasses[] = "sitedir-$sitedir";
3723
3724 // See Article:showDiffPage for class to support article diff styling
3725
3726 $underline = $lookupService->getOption( $user, 'underline' );
3727 if ( $underline < 2 ) {
3728 // The following classes can be used here:
3729 // * mw-underline-always
3730 // * mw-underline-never
3731 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3732 }
3733
3734 // Parser feature migration class
3735 // The idea is that this will eventually be removed, after the wikitext
3736 // which requires it is cleaned up.
3737 $bodyClasses[] = 'mw-hide-empty-elt';
3738
3739 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3740 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3741 $bodyClasses[] =
3742 'action-' . Sanitizer::escapeClass( $this->getContext()->getActionName() );
3743
3744 if ( $sk->isResponsive() ) {
3745 $bodyClasses[] = 'skin--responsive';
3746 }
3747
3748 $bodyAttrs = [];
3749 // While the expandClassList() is not strictly needed, it's used for backwards compatibility
3750 // (this used to be built as a string and hooks likely still expect that).
3751 $bodyAttrs['class'] = Html::expandClassList( $bodyClasses );
3752
3753 $this->getHookRunner()->onOutputPageBodyAttributes( $this, $sk, $bodyAttrs );
3754
3755 $pieces[] = Html::openElement( 'body', $bodyAttrs );
3756
3757 // Add dedicated ARIA live region container for notifications to assistive technology users.
3758 // Note that `aria-atomic="false"` and `aria-relevant="additions text"` are the default
3759 // values and therefore not duplicated below.
3760 $pieces[] = Html::rawElement( 'div', [
3761 'id' => 'mw-aria-live-region',
3762 'class' => 'mw-aria-live-region',
3763 'aria-live' => 'polite',
3764 ], '' );
3765
3766 return self::combineWrappedStrings( $pieces );
3767 }
3768
3774 public function getResourceLoader() {
3775 if ( $this->mResourceLoader === null ) {
3776 // Lazy-initialise as needed
3777 $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3778 }
3779 return $this->mResourceLoader;
3780 }
3781
3790 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3791 // Apply 'origin' filters
3792 $modules = $this->filterModules( (array)$modules, null, $only );
3793
3794 return RL\ClientHtml::makeLoad(
3795 $this->getRlClientContext(),
3796 $modules,
3797 $only,
3798 $extraQuery
3799 );
3800 }
3801
3808 protected static function combineWrappedStrings( array $chunks ) {
3809 // Filter out empty values
3810 $chunks = array_filter( $chunks, 'strlen' );
3811 return WrappedString::join( "\n", $chunks );
3812 }
3813
3820 public function getBottomScripts() {
3821 // Keep the hook appendage separate to preserve WrappedString objects.
3822 // This enables to merge them where possible.
3823 $extraHtml = '';
3824 $this->getHookRunner()->onSkinAfterBottomScripts( $this->getSkin(), $extraHtml );
3825
3826 $chunks = [];
3827 $chunks[] = $this->getRlClient()->getBodyHtml();
3828
3829 // Legacy non-ResourceLoader scripts
3830 $chunks[] = $this->mScripts;
3831
3832 // Keep hostname and backend time as the first variables for quick view-source access.
3833 // These other variables will form a very long inline blob.
3834 $vars = [];
3835 if ( $this->getConfig()->get( MainConfigNames::ShowHostnames ) ) {
3836 $vars['wgHostname'] = wfHostname();
3837 }
3838 $elapsed = $this->getRequest()->getElapsedTime();
3839 // seconds to milliseconds
3840 $vars['wgBackendResponseTime'] = round( $elapsed * 1000 );
3841
3842 $vars += $this->getJSVars( self::JS_VAR_LATE );
3843 if ( $this->limitReportJSData ) {
3844 $vars['wgPageParseReport'] = $this->limitReportJSData;
3845 }
3846
3847 $rlContext = $this->getRlClientContext();
3848 $chunks[] = ResourceLoader::makeInlineScript(
3849 'mw.config.set(' . $rlContext->encodeJson( $vars ) . ');'
3850 );
3851
3852 $chunks = [ self::combineWrappedStrings( $chunks ) ];
3853 if ( $extraHtml !== '' ) {
3854 $chunks[] = $extraHtml;
3855 }
3856
3857 return WrappedString::join( "\n", $chunks );
3858 }
3859
3866 public function getJsConfigVars() {
3867 return $this->mJsConfigVars;
3868 }
3869
3876 public function addJsConfigVars( $keys, $value = null ) {
3877 if ( is_array( $keys ) ) {
3878 foreach ( $keys as $key => $value ) {
3879 $this->mJsConfigVars[$key] = $value;
3880 }
3881 return;
3882 }
3883
3884 $this->mJsConfigVars[$keys] = $value;
3885 }
3886
3905 public function getJSVars( ?int $flag = null ) {
3906 $curRevisionId = 0;
3907 $articleId = 0;
3908 // T23115
3909 $canonicalSpecialPageName = false;
3910 $services = MediaWikiServices::getInstance();
3911
3912 $title = $this->getTitle();
3913 $ns = $title->getNamespace();
3914 $nsInfo = $services->getNamespaceInfo();
3915 $canonicalNamespace = $nsInfo->exists( $ns )
3916 ? $nsInfo->getCanonicalName( $ns )
3917 : $title->getNsText();
3918
3919 $sk = $this->getSkin();
3920 // Get the relevant title so that AJAX features can use the correct page name
3921 // when making API requests from certain special pages (T36972).
3922 $relevantTitle = $sk->getRelevantTitle();
3923
3924 if ( $ns === NS_SPECIAL ) {
3925 [ $canonicalSpecialPageName, ] =
3926 $services->getSpecialPageFactory()->
3927 resolveAlias( $title->getDBkey() );
3928 } elseif ( $this->canUseWikiPage() ) {
3929 $wikiPage = $this->getWikiPage();
3930 // If we already know that the latest revision ID is the same as the revision ID being viewed,
3931 // avoid fetching it again, as it may give inconsistent results (T339164).
3932 if ( $this->isRevisionCurrent() && $this->getRevisionId() ) {
3933 $curRevisionId = $this->getRevisionId();
3934 } else {
3935 $curRevisionId = $wikiPage->getLatest();
3936 }
3937 $articleId = $wikiPage->getId();
3938 }
3939
3940 // ParserOutput informs HTML/CSS via lang/dir attributes.
3941 // We inform JavaScript via mw.config from here.
3942 $lang = $this->getContentLangForJS();
3943
3944 // Pre-process information
3945 $separatorTransTable = $lang->separatorTransformTable();
3946 $separatorTransTable = $separatorTransTable ?: [];
3947 $compactSeparatorTransTable = [
3948 implode( "\t", array_keys( $separatorTransTable ) ),
3949 implode( "\t", $separatorTransTable ),
3950 ];
3951 $digitTransTable = $lang->digitTransformTable();
3952 $digitTransTable = $digitTransTable ?: [];
3953 $compactDigitTransTable = [
3954 implode( "\t", array_keys( $digitTransTable ) ),
3955 implode( "\t", $digitTransTable ),
3956 ];
3957
3958 $user = $this->getUser();
3959
3960 // Internal variables for MediaWiki core
3961 $vars = [
3962 // @internal For mediawiki.page.ready
3963 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3964
3965 // @internal For jquery.tablesorter
3966 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3967 'wgDigitTransformTable' => $compactDigitTransTable,
3968 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3969 'wgMonthNames' => $lang->getMonthNamesArray(),
3970
3971 // @internal For debugging purposes
3972 'wgRequestId' => WebRequest::getRequestId(),
3973 ];
3974
3975 // Start of supported and stable config vars (for use by extensions/gadgets).
3976 $vars += [
3977 'wgCanonicalNamespace' => $canonicalNamespace,
3978 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3979 'wgNamespaceNumber' => $title->getNamespace(),
3980 'wgPageName' => $title->getPrefixedDBkey(),
3981 'wgTitle' => $title->getText(),
3982 'wgCurRevisionId' => $curRevisionId,
3983 'wgRevisionId' => (int)$this->getRevisionId(),
3984 'wgArticleId' => $articleId,
3985 'wgIsArticle' => $this->isArticle(),
3986 'wgIsRedirect' => $title->isRedirect(),
3987 'wgAction' => $this->getContext()->getActionName(),
3988 'wgUserName' => $user->isAnon() ? null : $user->getName(),
3989 'wgUserGroups' => $services->getUserGroupManager()->getUserEffectiveGroups( $user ),
3990 'wgCategories' => $this->getCategories(),
3991 'wgPageViewLanguage' => $lang->getCode(),
3992 'wgPageContentLanguage' => $lang->getCode(),
3993 'wgPageContentModel' => $title->getContentModel(),
3994 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3995 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3996 ];
3997 if ( $user->isRegistered() ) {
3998 $vars['wgUserId'] = $user->getId();
3999 $vars['wgUserIsTemp'] = $user->isTemp();
4000 $vars['wgUserEditCount'] = $user->getEditCount();
4001 $userReg = $user->getRegistration();
4002 $vars['wgUserRegistration'] = $userReg ? (int)wfTimestamp( TS::UNIX, $userReg ) * 1000 : null;
4003 $userFirstReg = $services->getUserRegistrationLookup()->getFirstRegistration( $user );
4004 $vars['wgUserFirstRegistration'] = $userFirstReg ?
4005 (int)wfTimestamp( TS::UNIX, $userFirstReg ) * 1000 : null;
4006 // Get the revision ID of the oldest new message on the user's talk
4007 // page. This can be used for constructing new message alerts on
4008 // the client side.
4009 $userNewMsgRevId = $this->getLastSeenUserTalkRevId();
4010 // Only occupy precious space in the <head> when it is non-null (T53640)
4011 // mw.config.get returns null by default.
4012 if ( $userNewMsgRevId ) {
4013 $vars['wgUserNewMsgRevisionId'] = $userNewMsgRevId;
4014 }
4015 } else {
4016 $tempUserCreator = $services->getTempUserCreator();
4017 if ( $tempUserCreator->isEnabled() ) {
4018 // For logged-out users only (without a temporary account): get the user name that will
4019 // be used for their temporary account, if it has already been acquired.
4020 // This may be used in previews.
4021 $session = $this->getRequest()->getSession();
4022 $vars['wgTempUserName'] = $tempUserCreator->getStashedName( $session );
4023 }
4024 }
4025 $languageConverter = $services->getLanguageConverterFactory()
4026 ->getLanguageConverter( $title->getPageLanguage() );
4027 if ( $languageConverter->hasVariants() ) {
4028 $vars['wgUserVariant'] = $languageConverter->getPreferredVariant();
4029 }
4030 // Same test as SkinTemplate
4031 $vars['wgIsProbablyEditable'] = $this->getAuthority()->probablyCan( 'edit', $title );
4032 $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle &&
4033 $this->getAuthority()->probablyCan( 'edit', $relevantTitle );
4034 $restrictionStore = $services->getRestrictionStore();
4035 foreach ( $restrictionStore->listApplicableRestrictionTypes( $title ) as $type ) {
4036 // Following keys are set in $vars:
4037 // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
4038 $vars['wgRestriction' . ucfirst( $type )] = $restrictionStore->getRestrictions( $title, $type );
4039 }
4040 if ( $title->isMainPage() ) {
4041 $vars['wgIsMainPage'] = true;
4042 }
4043
4044 $relevantUser = $sk->getRelevantUser();
4045 if ( $relevantUser ) {
4046 $vars['wgRelevantUserName'] = $relevantUser->getName();
4047 }
4048 // End of stable config vars
4049
4050 $titleFormatter = $services->getTitleFormatter();
4051
4052 if ( $this->mRedirectedFrom ) {
4053 // @internal For skin JS
4054 $vars['wgRedirectedFrom'] = $titleFormatter->getPrefixedDBkey( $this->mRedirectedFrom );
4055 }
4056
4057 // Allow extensions to add their custom variables to the mw.config map.
4058 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
4059 // page-dependent but site-wide (without state).
4060 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
4061 $this->getHookRunner()->onMakeGlobalVariablesScript( $vars, $this );
4062
4063 // Merge in variables from addJsConfigVars last
4064 $vars = array_merge( $vars, $this->getJsConfigVars() );
4065
4066 // Return only early or late vars if requested
4067 if ( $flag !== null ) {
4068 $lateVarNames =
4069 array_fill_keys( self::CORE_LATE_JS_CONFIG_VAR_NAMES, true ) +
4070 array_fill_keys( ExtensionRegistry::getInstance()->getAttribute( 'LateJSConfigVarNames' ), true );
4071 foreach ( $vars as $name => $_ ) {
4072 // If the variable's late flag doesn't match the requested late flag, unset it
4073 if ( isset( $lateVarNames[ $name ] ) !== ( $flag === self::JS_VAR_LATE ) ) {
4074 unset( $vars[ $name ] );
4075 }
4076 }
4077 }
4078
4079 return $vars;
4080 }
4081
4087 private function getLastSeenUserTalkRevId() {
4088 $services = MediaWikiServices::getInstance();
4089 $user = $this->getUser();
4090 $userHasNewMessages = $services
4091 ->getTalkPageNotificationManager()
4092 ->userHasNewMessages( $user );
4093 if ( !$userHasNewMessages ) {
4094 return null;
4095 }
4096
4097 $timestamp = $services
4098 ->getTalkPageNotificationManager()
4099 ->getLatestSeenMessageTimestamp( $user );
4100 if ( !$timestamp ) {
4101 return null;
4102 }
4103
4104 $revRecord = $services->getRevisionLookup()->getRevisionByTimestamp(
4105 $user->getTalkPage(),
4106 $timestamp
4107 );
4108 return $revRecord ? $revRecord->getId() : null;
4109 }
4110
4120 public function userCanPreview() {
4121 $request = $this->getRequest();
4122 if (
4123 $request->getRawVal( 'action' ) !== 'submit' ||
4124 !$request->wasPosted()
4125 ) {
4126 return false;
4127 }
4128
4129 $user = $this->getUser();
4130
4131 if ( !$user->isRegistered() ) {
4132 // Anons have predictable edit tokens
4133 return false;
4134 }
4135 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
4136 return false;
4137 }
4138
4139 $title = $this->getTitle();
4140 if ( !$this->getAuthority()->probablyCan( 'edit', $title ) ) {
4141 return false;
4142 }
4143
4144 return true;
4145 }
4146
4150 public function getHeadLinksArray() {
4151 $tags = [];
4152 $config = $this->getConfig();
4153
4154 if ( $this->cspOutputMode === self::CSP_META ) {
4155 foreach ( $this->CSP->getDirectives() as $header => $directive ) {
4156 $tags["meta-csp-$header"] = Html::element( 'meta', [
4157 'http-equiv' => $header,
4158 'content' => $directive,
4159 ] );
4160 }
4161 }
4162
4163 $tags['meta-generator'] = Html::element( 'meta', [
4164 'name' => 'generator',
4165 'content' => 'MediaWiki ' . MW_VERSION,
4166 ] );
4167
4168 if ( $config->get( MainConfigNames::ReferrerPolicy ) !== false ) {
4169 // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
4170 // fallbacks should come before the primary value so we need to reverse the array.
4171 foreach ( array_reverse( (array)$config->get( MainConfigNames::ReferrerPolicy ) ) as $i => $policy ) {
4172 $tags["meta-referrer-$i"] = Html::element( 'meta', [
4173 'name' => 'referrer',
4174 'content' => $policy,
4175 ] );
4176 }
4177 }
4178
4179 $p = $this->getRobotsContent();
4180 if ( $p ) {
4181 // http://www.robotstxt.org/wc/meta-user.html
4182 // Only show if it's different from the default robots policy
4183 $tags['meta-robots'] = Html::element( 'meta', [
4184 'name' => 'robots',
4185 'content' => $p,
4186 ] );
4187 }
4188
4189 # Browser based phone number detection
4190 if ( $config->get( MainConfigNames::BrowserFormatDetection ) !== false ) {
4191 $tags['meta-format-detection'] = Html::element( 'meta', [
4192 'name' => 'format-detection',
4193 'content' => $config->get( MainConfigNames::BrowserFormatDetection ),
4194 ] );
4195 }
4196
4197 foreach ( $this->mMetatags as [ $name, $val ] ) {
4198 $attrs = [];
4199 if ( strncasecmp( $name, 'http:', 5 ) === 0 ) {
4200 $name = substr( $name, 5 );
4201 $attrs['http-equiv'] = $name;
4202 } elseif ( strncasecmp( $name, 'og:', 3 ) === 0 ) {
4203 $attrs['property'] = $name;
4204 } else {
4205 $attrs['name'] = $name;
4206 }
4207 $attrs['content'] = $val;
4208 $tagName = "meta-$name";
4209 if ( isset( $tags[$tagName] ) ) {
4210 $tagName .= $val;
4211 }
4212 $tags[$tagName] = Html::element( 'meta', $attrs );
4213 }
4214
4215 foreach ( $this->mLinktags as $tag ) {
4216 $tags[] = Html::element( 'link', $tag );
4217 }
4218
4219 if ( $config->get( MainConfigNames::UniversalEditButton ) && $this->isArticleRelated() ) {
4220 if ( $this->getAuthority()->probablyCan( 'edit', $this->getTitle() ) ) {
4221 $msg = $this->msg( 'edit' )->text();
4222 // Use mime type per https://phabricator.wikimedia.org/T21165#6946526
4223 $tags['universal-edit-button'] = Html::element( 'link', [
4224 'rel' => 'alternate',
4225 'type' => 'application/x-wiki',
4226 'title' => $msg,
4227 'href' => $this->getTitle()->getEditURL(),
4228 ] );
4229 }
4230 }
4231
4232 # Generally, the order of the favicon and apple-touch-icon links
4233 # should not matter, but Konqueror (3.5.9 at least) incorrectly
4234 # uses whichever one appears later in the HTML source. Make sure
4235 # apple-touch-icon is specified first to avoid this.
4236 $appleTouchIconHref = $config->get( MainConfigNames::AppleTouchIcon );
4237 # Browser look for those by default, unnecessary to set a link tag
4238 if (
4239 $appleTouchIconHref !== false &&
4240 $appleTouchIconHref !== '/apple-touch-icon.png' &&
4241 $appleTouchIconHref !== '/apple-touch-icon-precomposed.png'
4242 ) {
4243 $tags['apple-touch-icon'] = Html::element( 'link', [
4244 'rel' => 'apple-touch-icon',
4245 'href' => $appleTouchIconHref
4246 ] );
4247 }
4248
4249 $faviconHref = $config->get( MainConfigNames::Favicon );
4250 # Browser look for those by default, unnecessary to set a link tag
4251 if ( $faviconHref !== false && $faviconHref !== '/favicon.ico' ) {
4252 $tags['favicon'] = Html::element( 'link', [
4253 'rel' => 'icon',
4254 'href' => $faviconHref
4255 ] );
4256 }
4257
4258 # OpenSearch description link
4259 $tags['opensearch'] = Html::element( 'link', [
4260 'rel' => 'search',
4261 'type' => 'application/opensearchdescription+xml',
4262 'href' => wfScript( 'rest' ) . '/v1/search',
4263 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
4264 ] );
4265
4266 $services = MediaWikiServices::getInstance();
4267
4268 # Real Simple Discovery link, provides auto-discovery information
4269 # for the MediaWiki API (and potentially additional custom API
4270 # support such as WordPress or Twitter-compatible APIs for a
4271 # blogging extension, etc)
4272 $tags['rsd'] = Html::element( 'link', [
4273 'rel' => 'EditURI',
4274 'type' => 'application/rsd+xml',
4275 // Output a protocol-relative URL here if $wgServer is protocol-relative.
4276 // Whether RSD accepts relative or protocol-relative URLs is completely
4277 // undocumented, though.
4278 'href' => (string)$services->getUrlUtils()->expand( wfAppendQuery(
4279 wfScript( 'api' ),
4280 [ 'action' => 'rsd' ] ),
4282 ),
4283 ] );
4284
4285 $tags = array_merge(
4286 $tags,
4287 $this->getHeadLinksCanonicalURLArray( $config ),
4288 $this->getHeadLinksAlternateURLsArray(),
4289 $this->getHeadLinksCopyrightArray( $config ),
4290 $this->getHeadLinksSyndicationArray( $config ),
4291 );
4292
4293 // Allow extensions to add, remove and/or otherwise manipulate these links
4294 // If you want only to *add* <head> links, please use the addHeadItem()
4295 // (or addHeadItems() for multiple items) method instead.
4296 // This hook is provided as a last resort for extensions to modify these
4297 // links before the output is sent to client.
4298 $this->getHookRunner()->onOutputPageAfterGetHeadLinksArray( $tags, $this );
4299
4300 return $tags;
4301 }
4302
4322 private function getHeadLinksCanonicalURLArray( Config $config ) {
4323 $tags = [];
4324 $canonicalUrl = $this->mCanonicalUrl;
4325
4326 if ( $config->get( MainConfigNames::EnableCanonicalServerLink ) ) {
4327 $query = [];
4328 $action = $this->getContext()->getActionName();
4329 $isCanonicalUrlAction = in_array( $action, [ 'history', 'info' ] );
4330 $services = MediaWikiServices::getInstance();
4331 $languageConverterFactory = $services->getLanguageConverterFactory();
4332 $isLangConversionDisabled = $languageConverterFactory->isConversionDisabled();
4333 $pageLang = $this->getTitle()->getPageLanguage();
4334 $pageLanguageConverter = $languageConverterFactory->getLanguageConverter( $pageLang );
4335 $urlVariant = $pageLanguageConverter->getURLVariant();
4336
4337 if ( $canonicalUrl !== false ) {
4338 $canonicalUrl = (string)$services->getUrlUtils()->expand( $canonicalUrl, PROTO_CANONICAL );
4339 } elseif ( $this->isArticleRelated() ) {
4340 if ( $isCanonicalUrlAction ) {
4341 $query['action'] = $action;
4342 } elseif ( !$isLangConversionDisabled && $urlVariant ) {
4343 # T54429, T108443: Making canonical URL language-variant-aware.
4344 $query['variant'] = $urlVariant;
4345 }
4346 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
4347 } else {
4348 $reqUrl = $this->getRequest()->getRequestURL();
4349 $canonicalUrl = (string)$services->getUrlUtils()->expand( $reqUrl, PROTO_CANONICAL );
4350 }
4351 }
4352
4353 if ( $canonicalUrl !== false ) {
4354 $tags['link-canonical'] = Html::element( 'link', [
4355 'rel' => 'canonical',
4356 'href' => $canonicalUrl
4357 ] );
4358 }
4359
4360 return $tags;
4361 }
4362
4371 private function getHeadLinksAlternateURLsArray() {
4372 $tags = [];
4373 $languageUrls = [];
4374 $action = $this->getContext()->getActionName();
4375 $isCanonicalUrlAction = in_array( $action, [ 'history', 'info' ] );
4376 $services = MediaWikiServices::getInstance();
4377 $languageConverterFactory = $services->getLanguageConverterFactory();
4378 $isLangConversionDisabled = $languageConverterFactory->isConversionDisabled();
4379 $pageLang = $this->getTitle()->getPageLanguage();
4380 $pageLanguageConverter = $languageConverterFactory->getLanguageConverter( $pageLang );
4381
4382 # Language variants
4383 if (
4384 $this->isArticleRelated() &&
4385 !$isCanonicalUrlAction &&
4386 $pageLanguageConverter->hasVariants() &&
4387 !$isLangConversionDisabled
4388 ) {
4389 $variants = $pageLanguageConverter->getVariants();
4390 foreach ( $variants as $variant ) {
4391 $bcp47 = LanguageCode::bcp47( $variant );
4392 $languageUrls[$bcp47] = $this->getTitle()
4393 ->getFullURL( [ 'variant' => $variant ], false, PROTO_CURRENT );
4394 }
4395 }
4396
4397 # Alternate URLs for interlanguage links would be handled in HTML body tag instead of
4398 # head tag, see T326829.
4399
4400 if ( $languageUrls ) {
4401 # Force the alternate URL of page language code to be self.
4402 # T123901, T305540, T108443: Override mixed-variant variant link in language variant links.
4403 $currentUrl = $this->getTitle()->getFullURL( [], false, PROTO_CURRENT );
4404 $pageLangCodeBcp47 = LanguageCode::bcp47( $pageLang->getCode() );
4405 $languageUrls[$pageLangCodeBcp47] = $currentUrl;
4406
4407 ksort( $languageUrls );
4408
4409 # Also add x-default link per https://support.google.com/webmasters/answer/189077?hl=en
4410 $languageUrls['x-default'] = $currentUrl;
4411
4412 # Process all of language variants and interlanguage links
4413 foreach ( $languageUrls as $bcp47 => $languageUrl ) {
4414 $bcp47lowercase = strtolower( $bcp47 );
4415 $tags['link-alternate-language-' . $bcp47lowercase] = Html::element( 'link', [
4416 'rel' => 'alternate',
4417 'hreflang' => $bcp47,
4418 'href' => $languageUrl,
4419 ] );
4420 }
4421 }
4422
4423 return $tags;
4424 }
4425
4432 private function getHeadLinksCopyrightArray( Config $config ) {
4433 $tags = [];
4434
4435 if ( $this->copyrightUrl !== null ) {
4436 $copyright = $this->copyrightUrl;
4437 } else {
4438 $copyright = '';
4439 if ( $config->get( MainConfigNames::RightsPage ) ) {
4440 $copy = Title::newFromText( $config->get( MainConfigNames::RightsPage ) );
4441
4442 if ( $copy ) {
4443 $copyright = $copy->getLocalURL();
4444 }
4445 }
4446
4447 if ( !$copyright && $config->get( MainConfigNames::RightsUrl ) ) {
4448 $copyright = $config->get( MainConfigNames::RightsUrl );
4449 }
4450 }
4451
4452 if ( $copyright ) {
4453 $tags['copyright'] = Html::element( 'link', [
4454 'rel' => 'license',
4455 'href' => $copyright
4456 ] );
4457 }
4458
4459 return $tags;
4460 }
4461
4468 private function getHeadLinksSyndicationArray( Config $config ) {
4469 if ( !$config->get( MainConfigNames::Feed ) ) {
4470 return [];
4471 }
4472
4473 $tags = [];
4474 $feedLinks = [];
4475
4476 foreach ( $this->getSyndicationLinks() as $format => $link ) {
4477 # Use the page name for the title. In principle, this could
4478 # lead to issues with having the same name for different feeds
4479 # corresponding to the same page, but we can't avoid that at
4480 # this low a level.
4481
4482 $feedLinks[] = $this->feedLink(
4483 $format,
4484 $link,
4485 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
4486 $this->msg(
4487 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
4488 )->text()
4489 );
4490 }
4491
4492 # Recent changes feed should appear on every page (except recentchanges,
4493 # that would be redundant). Put it after the per-page feed to avoid
4494 # changing existing behavior. It's still available, probably via a
4495 # menu in your browser. Some sites might have a different feed they'd
4496 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
4497 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
4498 # If so, use it instead.
4499 $sitename = $config->get( MainConfigNames::Sitename );
4500 $overrideSiteFeed = $config->get( MainConfigNames::OverrideSiteFeed );
4501 if ( $overrideSiteFeed ) {
4502 foreach ( $overrideSiteFeed as $type => $feedUrl ) {
4503 // Note, this->feedLink escapes the url.
4504 $feedLinks[] = $this->feedLink(
4505 $type,
4506 $feedUrl,
4507 $this->msg( "site-{$type}-feed", $sitename )->text()
4508 );
4509 }
4510 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
4511 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
4512 foreach ( $this->getAdvertisedFeedTypes() as $format ) {
4513 $feedLinks[] = $this->feedLink(
4514 $format,
4515 $rctitle->getLocalURL( [ 'feed' => $format ] ),
4516 # For grep: 'site-rss-feed', 'site-atom-feed'
4517 $this->msg( "site-{$format}-feed", $sitename )->text()
4518 );
4519 }
4520 }
4521
4522 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
4523 # manipulating or removing existing feed tags. If you want to add new feeds, you should
4524 # use OutputPage::addFeedLink() instead.
4525 $this->getHookRunner()->onAfterBuildFeedLinks( $feedLinks );
4526
4527 $tags += $feedLinks;
4528
4529 return $tags;
4530 }
4531
4540 private function feedLink( $type, $url, $text ) {
4541 return Html::element( 'link', [
4542 'rel' => 'alternate',
4543 'type' => "application/$type+xml",
4544 'title' => $text,
4545 'href' => $url ]
4546 );
4547 }
4548
4558 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
4559 $options = [];
4560 if ( $media ) {
4561 $options['media'] = $media;
4562 }
4563 if ( $condition ) {
4564 $options['condition'] = $condition;
4565 }
4566 if ( $dir ) {
4567 $options['dir'] = $dir;
4568 }
4569 $this->styles[$style] = $options;
4570 }
4571
4580 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
4581 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
4582 # If wanted, and the interface is right-to-left, flip the CSS
4583 $style_css = CSSJanus::transform( $style_css, true, false );
4584 }
4585 $this->mInlineStyles .= Html::inlineStyle( $style_css );
4586 }
4587
4593 protected function buildExemptModules() {
4594 $chunks = [];
4595
4596 // Requirements:
4597 // - Within modules provided by the software (core, skin, extensions),
4598 // styles from skin stylesheets should be overridden by styles
4599 // from modules dynamically loaded with JavaScript.
4600 // - Styles from site-specific, private, and user modules should override
4601 // both of the above.
4602 //
4603 // The effective order for stylesheets must thus be:
4604 // 1. Page style modules, formatted server-side by RL\ClientHtml.
4605 // 2. Dynamically-loaded styles, inserted client-side by mw.loader.
4606 // 3. Styles that are site-specific, private or from the user, formatted
4607 // server-side by this function.
4608 //
4609 // The 'ResourceLoaderDynamicStyles' marker helps JavaScript know where
4610 // point #2 is.
4611
4612 // Add legacy styles added through addStyle()/addInlineStyle() here
4613 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
4614
4615 // Things that go after the ResourceLoaderDynamicStyles marker
4616 $append = [];
4617 $separateReq = [ 'site.styles', 'user.styles' ];
4618 foreach ( $this->rlExemptStyleModules as $moduleNames ) {
4619 if ( $moduleNames ) {
4620 $append[] = $this->makeResourceLoaderLink(
4621 array_diff( $moduleNames, $separateReq ),
4622 RL\Module::TYPE_STYLES
4623 );
4624
4625 foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
4626 // These require their own dedicated request in order to support "@import"
4627 // syntax, which is incompatible with concatenation. (T147667, T37562)
4628 $append[] = $this->makeResourceLoaderLink( $name,
4629 RL\Module::TYPE_STYLES
4630 );
4631 }
4632 }
4633 }
4634 if ( $append ) {
4635 $chunks[] = Html::element(
4636 'meta',
4637 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
4638 );
4639 $chunks = array_merge( $chunks, $append );
4640 }
4641
4642 return self::combineWrappedStrings( $chunks );
4643 }
4644
4648 public function buildCssLinksArray() {
4649 $links = [];
4650
4651 foreach ( $this->styles as $file => $options ) {
4652 $link = $this->styleLink( $file, $options );
4653 if ( $link ) {
4654 $links[$file] = $link;
4655 }
4656 }
4657 return $links;
4658 }
4659
4667 protected function styleLink( $style, array $options ) {
4668 if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
4669 return '';
4670 }
4671
4672 if ( isset( $options['media'] ) ) {
4673 $media = self::transformCssMedia( $options['media'], $this->getRequest() );
4674 if ( $media === null ) {
4675 return '';
4676 }
4677 } else {
4678 $media = 'all';
4679 }
4680
4681 if ( str_starts_with( $style, '/' ) ||
4682 str_starts_with( $style, 'http:' ) ||
4683 str_starts_with( $style, 'https:' )
4684 ) {
4685 $url = $style;
4686 } else {
4687 $config = $this->getConfig();
4688 // Append file hash as query parameter
4689 $url = self::transformResourcePath(
4690 $config,
4691 $config->get( MainConfigNames::StylePath ) . '/' . $style
4692 );
4693 }
4694
4695 $link = Html::linkedStyle( $url, $media );
4696
4697 if ( isset( $options['condition'] ) ) {
4698 $condition = htmlspecialchars( $options['condition'] );
4699 $link = "<!--[if $condition]>$link<![endif]-->";
4700 }
4701 return $link;
4702 }
4703
4725 public static function transformResourcePath( Config $config, $path ) {
4726 $localDir = MW_INSTALL_PATH;
4727 $remotePathPrefix = $config->get( MainConfigNames::ResourceBasePath );
4728 if ( $remotePathPrefix === '' ) {
4729 // The configured base path is required to be empty string for
4730 // wikis in the domain root
4731 $remotePath = '/';
4732 } else {
4733 $remotePath = $remotePathPrefix;
4734 }
4735 if ( !str_starts_with( $path, $remotePath ) || str_starts_with( $path, '//' ) ) {
4736 // - Path is outside wgResourceBasePath, ignore.
4737 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
4738 return $path;
4739 }
4740 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
4741 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
4742 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
4743 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
4744 $uploadPath = $config->get( MainConfigNames::UploadPath );
4745 if ( str_starts_with( $path, $uploadPath ) ) {
4746 $localDir = $config->get( MainConfigNames::UploadDirectory );
4747 $remotePathPrefix = $remotePath = $uploadPath;
4748 }
4749
4750 $path = RelPath::getRelativePath( $path, $remotePath );
4751 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
4752 }
4753
4765 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
4766 // This MUST match the equivalent logic in CSSMin::remapOne()
4767 $localFile = "$localPath/$file";
4768 $url = "$remotePathPrefix/$file";
4769 if ( is_file( $localFile ) ) {
4770 $hash = md5_file( $localFile );
4771 if ( $hash === false ) {
4772 wfLogWarning( __METHOD__ . ": Failed to hash $localFile" );
4773 $hash = '';
4774 }
4775 $url .= '?' . substr( $hash, 0, 5 );
4776 }
4777 return $url;
4778 }
4779
4788 public static function transformCssMedia( $media, WebRequest $request ) {
4789 if ( $request->getBool( 'printable' ) ) {
4790 // When browsing with printable=yes, apply "print" media styles
4791 // as if they are screen styles (no media, media="").
4792 if ( $media === 'print' ) {
4793 return '';
4794 }
4795
4796 // https://www.w3.org/TR/css3-mediaqueries/#syntax
4797 //
4798 // This regex will not attempt to understand a comma-separated media_query_list
4799 // Example supported values for $media:
4800 //
4801 // 'screen', 'only screen', 'screen and (min-width: 982px)' ),
4802 //
4803 // Example NOT supported value for $media:
4804 //
4805 // '3d-glasses, screen, print and resolution > 90dpi'
4806 //
4807 // If it's a "printable" request, we disable all screen stylesheets.
4808 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
4809 if ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
4810 return null;
4811 }
4812 }
4813
4814 return $media;
4815 }
4816
4825 public function addWikiMsg( $name, ...$args ) {
4826 $this->addWikiMsgArray( $name, $args );
4827 }
4828
4838 public function addWikiMsgArray( $name, $args ) {
4839 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
4840 }
4841
4868 public function wrapWikiMsg( $wrap, ...$msgSpecs ) {
4869 $s = $wrap;
4870 foreach ( $msgSpecs as $n => $spec ) {
4871 if ( is_array( $spec ) ) {
4872 $args = $spec;
4873 $name = array_shift( $args );
4874 } else {
4875 $args = [];
4876 $name = $spec;
4877 }
4878 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4879 }
4880
4881 $title = $this->getTitle();
4882 if ( $title === null ) {
4883 throw new RuntimeException( 'No title in ' . __METHOD__ );
4884 }
4885 $popts = $this->internalParserOptions( true );
4886 // We are *mostly* parsing a message. Other code wants to rely on that. (T395196)
4887 // It would be cleaner if the wrappers were added outside of wikitext parsing, so we could
4888 // really just parse the message, but it seems scary to change that now.
4889 $popts->setIsMessage( true );
4890 $this->addWikiTextTitleInternal( $s, $title, /*linestart*/ true, $popts );
4891 }
4892
4899 public function isTOCEnabled() {
4900 return $this->mEnableTOC;
4901 }
4902
4911 public function addTOCPlaceholder( TOCData $tocData, bool $prepend = false ): void {
4912 $pout = new ParserOutput;
4913 $pout->setTOCData( $tocData );
4914 $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC );
4915 $pout->setContentHolderText( Parser::TOC_PLACEHOLDER );
4916 if ( $prepend ) {
4917 $text = $this->getParserOutputText( $pout, $this->internalParserOptions( false ), [] );
4918 $this->addParserOutputMetadata( $pout );
4919 $this->prependHTML( $text );
4920 } else {
4921 $this->addParserOutput( $pout, $this->internalParserOptions( false ) );
4922 }
4923 }
4924
4932 public static function setupOOUI( $skinName = null, $dir = null ) {
4933 if ( !self::$oouiSetupDone ) {
4934 self::$oouiSetupDone = true;
4935 $context = RequestContext::getMain();
4936 $skinName = $context->getSkinName();
4937 $dir = $context->getLanguage()->getDir();
4938 $themes = RL\OOUIFileModule::getSkinThemeMap();
4939 $theme = $themes[$skinName] ?? $themes['default'];
4940 // For example, 'OOUI\WikimediaUITheme'.
4941 $themeClass = "OOUI\\{$theme}Theme";
4942 Theme::setSingleton( new $themeClass() );
4943 Element::setDefaultDir( $dir );
4944 }
4945 }
4946
4952 public static function resetOOUI() {
4953 if ( self::$oouiSetupDone ) {
4954 self::$oouiSetupDone = false;
4955 self::setupOOUI();
4956 }
4957 }
4958
4965 public function enableOOUI() {
4966 self::setupOOUI();
4967 $this->addModuleStyles( [
4968 'oojs-ui-core.styles',
4969 'oojs-ui.styles.indicators',
4970 'mediawiki.widgets.styles',
4971 'oojs-ui-core.icons',
4972 ] );
4973 }
4974
4981 public function getCSP() {
4982 return $this->CSP;
4983 }
4984
4998 public function setCspOutputMode( string $mode ): void {
4999 $this->cspOutputMode = $mode;
5000 }
5001
5011 public function tailElement( $skin ) {
5012 $tail = [
5013 MWDebug::getDebugHTML( $skin ),
5014 $this->getBottomScripts(),
5015 MWDebug::getHTMLDebugLog(),
5016 Html::closeElement( 'body' ),
5017 Html::closeElement( 'html' ),
5018 ];
5019
5020 return WrappedStringList::join( "\n", $tail );
5021 }
5022}
5023
5025class_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.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
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:1088
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.
addParserOutputContent(ParserOutput $parserOutput, ParserOptions $parserOptions, ?array $poOptions=null,)
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
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.
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.
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.
getHTMLTitle()
Return the "HTML title", i.e.
static transformCssMedia( $media, WebRequest $request)
Transform "media" attribute based on request parameters.
getFeedAppendQuery()
Will currently always return null.
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
getModuleStyles( $filter=false)
Get the list of style-only modules to load on this page.
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...
addParserOutputText(string $text, $poOptions=[])
Add the HTML associated with a ParserOutput object, without any metadata.
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.
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.
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.
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.
addParserOutput(ParserOutput $parserOutput, ParserOptions $parserOptions, ?array $poOptions=null,)
Add everything from a ParserOutput object.
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?
buildExemptModules()
Build exempt modules and legacy non-ResourceLoader styles.
addTOCPlaceholder(TOCData $tocData, bool $prepend=false)
Helper function to add a Table of Contents to the output.
string $mLastModified
Used for sending cache control.
clearHTML()
Clear the body HTML.
setPageTitle( $name)
"Page title" means the contents of <h1>.
setCopyright( $hasCopyright)
Set whether the standard copyright should be shown for the current page.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
setPageTitleMsg(Message $msg)
"Page title" means the contents of <h1>.
userCanPreview()
To make it harder for someone to slip a user a fake JavaScript or CSS preview, a random token is asso...
addFeedLink( $format, $href)
Add a feed link to the page header.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
getCanonicalUrl()
Returns the URL to be used for the <link rel=canonical>> if one is set.
static combineWrappedStrings(array $chunks)
Combine WrappedString chunks and filter out empty ones.
isTOCEnabled()
Whether the output has a table of contents when the ToC is rendered inline.
setPrintable()
Set the page as printable, i.e.
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
setProperty( $name, $value)
Set an additional output property.
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,...
getModules( $filter=false)
Get the list of modules to include on this page.
__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.
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.
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.
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.
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:66
Page existence and metadata cache.
Definition LinkCache.php:53
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.
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.
setContentHolderText(?string $text)
Sets the body fragment text of the ParserOutput.
setSections(array $sectionArray)
getExtraCSPScriptSrcs()
Get extra Content-Security-Policy 'script-src' directives.
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:138
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:34
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:35
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:707
getOptions()
Get current skin's options.
Definition Skin.php:2458
getHtmlElementAttributes()
Return values for <html> element.
Definition Skin.php:749
isResponsive()
Indicates if this skin is responsive.
Definition Skin.php:367
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'=>[], 'DefaultLockManager'=> null, 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> 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, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'RestTermsOfServiceUrl'=> null, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'EnableSectionShare'=> false, 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'RestrictUserPageEditing' => false, 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', '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', 'managesessions' => '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, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, '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: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'RestLocalModuleTestBaseUrl' => null, 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'DefaultLockManager' => [ 'string', 'null', ], 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'RestTermsOfServiceUrl' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', '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', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'RestrictUserPageEditing' => 'boolean', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'ReauthenticateForActions' => 'object', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RestrictedTagViewRights' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'RestLocalModuleTestBaseUrl' => [ 'string', 'null', ], 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', ], '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', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'mode' => [ 'type' => 'string', ], ], 'required' => [ 'mode', ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Interface for 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)