MediaWiki fundraising/REL1_35
OutputPage.php
Go to the documentation of this file.
1<?php
23use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
28use Wikimedia\RelPath;
29use Wikimedia\WrappedString;
30use Wikimedia\WrappedStringList;
31
48 use ProtectedHookAccessorTrait;
49
51 protected $mMetatags = [];
52
54 protected $mLinktags = [];
55
57 protected $mCanonicalUrl = false;
58
62 private $mPageTitle = '';
63
72
74 private $cacheIsFinal = false;
75
80 public $mBodytext = '';
81
83 private $mHTMLtitle = '';
84
89 private $mIsArticle = false;
90
92 private $mIsArticleRelated = true;
93
95 private $mHasCopyright = false;
96
101 private $mPrintable = false;
102
107 private $mSubtitle = [];
108
110 public $mRedirect = '';
111
113 protected $mStatusCode;
114
119 protected $mLastModified = '';
120
122 protected $mCategoryLinks = [];
123
125 protected $mCategories = [
126 'hidden' => [],
127 'normal' => [],
128 ];
129
131 protected $mIndicators = [];
132
134 private $mLanguageLinks = [];
135
142 private $mScripts = '';
143
145 protected $mInlineStyles = '';
146
151 public $mPageLinkTitle = '';
152
158
160 protected $mHeadItems = [];
161
164
166 protected $mModules = [];
167
169 protected $mModuleStyles = [];
170
173
175 private $rlClient;
176
179
182
184 protected $mJsConfigVars = [];
185
187 protected $mTemplateIds = [];
188
190 protected $mImageTimeKeys = [];
191
193 public $mRedirectCode = '';
194
195 protected $mFeedLinksAppendQuery = null;
196
202 protected $mAllowedModules = [
203 ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL,
204 ];
205
207 protected $mDoNothing = false;
208
209 // Parser related.
210
212 protected $mContainsNewMagic = 0;
213
218 protected $mParserOptions = null;
219
225 private $mFeedLinks = [];
226
227 // Gwicke work on squid caching? Roughly from 2003.
228 protected $mEnableClientCache = true;
229
231 private $mArticleBodyOnly = false;
232
234 protected $mNewSectionLink = false;
235
237 protected $mHideNewSectionLink = false;
238
244 public $mNoGallery = false;
245
247 protected $mCdnMaxage = 0;
249 protected $mCdnMaxageLimit = INF;
250
256 protected $mPreventClickjacking = true;
257
259 private $mRevisionId = null;
260
262 private $mRevisionTimestamp = null;
263
265 protected $mFileVersion = null;
266
275 protected $styles = [];
276
277 private $mIndexPolicy = 'index';
278 private $mFollowPolicy = 'follow';
279
285 private $mVaryHeader = [
286 'Accept-Encoding' => null,
287 ];
288
295 private $mRedirectedFrom = null;
296
300 private $mProperties = [];
301
305 private $mTarget = null;
306
310 private $mEnableTOC = false;
311
316
318 private $limitReportJSData = [];
319
321 private $contentOverrides = [];
322
325
329 private $mLinkHeader = [];
330
334 private $CSP;
335
339 private static $cacheVaryCookies = null;
340
347 public function __construct( IContextSource $context ) {
348 $this->setContext( $context );
349 $this->CSP = new ContentSecurityPolicy(
350 $context->getRequest()->response(),
351 $context->getConfig(),
352 $this->getHookContainer()
353 );
354 }
355
362 public function redirect( $url, $responsecode = '302' ) {
363 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
364 $this->mRedirect = str_replace( "\n", '', $url );
365 $this->mRedirectCode = (string)$responsecode;
366 }
367
373 public function getRedirect() {
374 return $this->mRedirect;
375 }
376
385 public function setCopyrightUrl( $url ) {
386 $this->copyrightUrl = $url;
387 }
388
394 public function setStatusCode( $statusCode ) {
395 $this->mStatusCode = $statusCode;
396 }
397
405 public function addMeta( $name, $val ) {
406 $this->mMetatags[] = [ $name, $val ];
407 }
408
415 public function getMetaTags() {
416 return $this->mMetatags;
417 }
418
426 public function addLink( array $linkarr ) {
427 $this->mLinktags[] = $linkarr;
428 }
429
436 public function getLinkTags() {
437 return $this->mLinktags;
438 }
439
445 public function setCanonicalUrl( $url ) {
446 $this->mCanonicalUrl = $url;
447 }
448
456 public function getCanonicalUrl() {
457 return $this->mCanonicalUrl;
458 }
459
467 public function addScript( $script ) {
468 $this->mScripts .= $script;
469 }
470
479 public function addScriptFile( $file, $unused = null ) {
480 $this->addScript( Html::linkedScript( $file, $this->CSP->getNonce() ) );
481 }
482
489 public function addInlineScript( $script ) {
490 $this->mScripts .= Html::inlineScript( "\n$script\n", $this->CSP->getNonce() ) . "\n";
491 }
492
501 protected function filterModules( array $modules, $position = null,
502 $type = ResourceLoaderModule::TYPE_COMBINED
503 ) {
504 $resourceLoader = $this->getResourceLoader();
505 $filteredModules = [];
506 foreach ( $modules as $val ) {
507 $module = $resourceLoader->getModule( $val );
508 if ( $module instanceof ResourceLoaderModule
509 && $module->getOrigin() <= $this->getAllowedModules( $type )
510 ) {
511 if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
512 $this->warnModuleTargetFilter( $module->getName() );
513 continue;
514 }
515 $filteredModules[] = $val;
516 }
517 }
518 return $filteredModules;
519 }
520
521 private function warnModuleTargetFilter( $moduleName ) {
522 static $warnings = [];
523 if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
524 return;
525 }
526 $warnings[$this->mTarget][$moduleName] = true;
527 $this->getResourceLoader()->getLogger()->debug(
528 'Module "{module}" not loadable on target "{target}".',
529 [
530 'module' => $moduleName,
531 'target' => $this->mTarget,
532 ]
533 );
534 }
535
545 public function getModules( $filter = false, $position = null, $param = 'mModules',
546 $type = ResourceLoaderModule::TYPE_COMBINED
547 ) {
548 $modules = array_values( array_unique( $this->$param ) );
549 return $filter
550 ? $this->filterModules( $modules, null, $type )
551 : $modules;
552 }
553
559 public function addModules( $modules ) {
560 $this->mModules = array_merge( $this->mModules, (array)$modules );
561 }
562
570 public function getModuleStyles( $filter = false, $position = null ) {
571 return $this->getModules( $filter, null, 'mModuleStyles',
572 ResourceLoaderModule::TYPE_STYLES
573 );
574 }
575
585 public function addModuleStyles( $modules ) {
586 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
587 }
588
592 public function getTarget() {
593 return $this->mTarget;
594 }
595
601 public function setTarget( $target ) {
602 $this->mTarget = $target;
603 }
604
612 public function addContentOverride( LinkTarget $target, Content $content ) {
613 if ( !$this->contentOverrides ) {
614 // Register a callback for $this->contentOverrides on the first call
615 $this->addContentOverrideCallback( function ( LinkTarget $target ) {
616 $key = $target->getNamespace() . ':' . $target->getDBkey();
617 return $this->contentOverrides[$key] ?? null;
618 } );
619 }
620
621 $key = $target->getNamespace() . ':' . $target->getDBkey();
622 $this->contentOverrides[$key] = $content;
623 }
624
632 public function addContentOverrideCallback( callable $callback ) {
633 $this->contentOverrideCallbacks[] = $callback;
634 }
635
643 public function addHtmlClasses( $classes ) {
644 $this->mAdditionalHtmlClasses = array_merge( $this->mAdditionalHtmlClasses, (array)$classes );
645 }
646
652 public function getHeadItemsArray() {
653 return $this->mHeadItems;
654 }
655
668 public function addHeadItem( $name, $value ) {
669 $this->mHeadItems[$name] = $value;
670 }
671
678 public function addHeadItems( $values ) {
679 $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
680 }
681
688 public function hasHeadItem( $name ) {
689 return isset( $this->mHeadItems[$name] );
690 }
691
698 public function addBodyClasses( $classes ) {
699 $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
700 }
701
709 public function setArticleBodyOnly( $only ) {
710 $this->mArticleBodyOnly = $only;
711 }
712
718 public function getArticleBodyOnly() {
719 return $this->mArticleBodyOnly;
720 }
721
729 public function setProperty( $name, $value ) {
730 $this->mProperties[$name] = $value;
731 }
732
740 public function getProperty( $name ) {
741 return $this->mProperties[$name] ?? null;
742 }
743
755 public function checkLastModified( $timestamp ) {
756 if ( !$timestamp || $timestamp == '19700101000000' ) {
757 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP" );
758 return false;
759 }
760 $config = $this->getConfig();
761 if ( !$config->get( 'CachePages' ) ) {
762 wfDebug( __METHOD__ . ": CACHE DISABLED" );
763 return false;
764 }
765
766 $timestamp = wfTimestamp( TS_MW, $timestamp );
767 $modifiedTimes = [
768 'page' => $timestamp,
769 'user' => $this->getUser()->getTouched(),
770 'epoch' => $config->get( 'CacheEpoch' )
771 ];
772 if ( $config->get( 'UseCdn' ) ) {
773 $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $this->getCdnCacheEpoch(
774 time(),
775 $config->get( 'CdnMaxAge' )
776 ) );
777 }
778 $this->getHookRunner()->onOutputPageCheckLastModified( $modifiedTimes, $this );
779
780 $maxModified = max( $modifiedTimes );
781 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
782
783 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
784 if ( $clientHeader === false ) {
785 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
786 return false;
787 }
788
789 # IE sends sizes after the date like this:
790 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
791 # this breaks strtotime().
792 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
793
794 Wikimedia\suppressWarnings(); // E_STRICT system time warnings
795 $clientHeaderTime = strtotime( $clientHeader );
796 Wikimedia\restoreWarnings();
797 if ( !$clientHeaderTime ) {
798 wfDebug( __METHOD__
799 . ": unable to parse the client's If-Modified-Since header: $clientHeader" );
800 return false;
801 }
802 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
803
804 # Make debug info
805 $info = '';
806 foreach ( $modifiedTimes as $name => $value ) {
807 if ( $info !== '' ) {
808 $info .= ', ';
809 }
810 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
811 }
812
813 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
814 wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
815 wfDebug( __METHOD__ . ": effective Last-Modified: " .
816 wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
817 if ( $clientHeaderTime < $maxModified ) {
818 wfDebug( __METHOD__ . ": STALE, $info", 'private' );
819 return false;
820 }
821
822 # Not modified
823 # Give a 304 Not Modified response code and disable body output
824 wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
825 ini_set( 'zlib.output_compression', 0 );
826 $this->getRequest()->response()->statusHeader( 304 );
827 $this->sendCacheControl();
828 $this->disable();
829
830 // Don't output a compressed blob when using ob_gzhandler;
831 // it's technically against HTTP spec and seems to confuse
832 // Firefox when the response gets split over two packets.
834
835 return true;
836 }
837
843 private function getCdnCacheEpoch( $reqTime, $maxAge ) {
844 // Ensure Last-Modified is never more than $wgCdnMaxAge in the past,
845 // because even if the wiki page content hasn't changed since, static
846 // resources may have changed (skin HTML, interface messages, urls, etc.)
847 // and must roll-over in a timely manner (T46570)
848 return $reqTime - $maxAge;
849 }
850
857 public function setLastModified( $timestamp ) {
858 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
859 }
860
869 public function setRobotPolicy( $policy ) {
870 $policy = Article::formatRobotPolicy( $policy );
871
872 if ( isset( $policy['index'] ) ) {
873 $this->setIndexPolicy( $policy['index'] );
874 }
875 if ( isset( $policy['follow'] ) ) {
876 $this->setFollowPolicy( $policy['follow'] );
877 }
878 }
879
886 public function getRobotPolicy() {
887 return "{$this->mIndexPolicy},{$this->mFollowPolicy}";
888 }
889
897 public function setIndexPolicy( $policy ) {
898 $policy = trim( $policy );
899 if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
900 $this->mIndexPolicy = $policy;
901 }
902 }
903
909 public function getIndexPolicy() {
910 return $this->mIndexPolicy;
911 }
912
920 public function setFollowPolicy( $policy ) {
921 $policy = trim( $policy );
922 if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
923 $this->mFollowPolicy = $policy;
924 }
925 }
926
932 public function getFollowPolicy() {
933 return $this->mFollowPolicy;
934 }
935
942 public function setHTMLTitle( $name ) {
943 if ( $name instanceof Message ) {
944 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
945 } else {
946 $this->mHTMLtitle = $name;
947 }
948 }
949
955 public function getHTMLTitle() {
956 return $this->mHTMLtitle;
957 }
958
964 public function setRedirectedFrom( $t ) {
965 $this->mRedirectedFrom = $t;
966 }
967
980 public function setPageTitle( $name ) {
981 if ( $name instanceof Message ) {
982 $name = $name->setContext( $this->getContext() )->text();
983 }
984
985 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
986 # but leave "<i>foobar</i>" alone
987 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
988 $this->mPageTitle = $nameWithTags;
989
990 # change "<i>foo&amp;bar</i>" to "foo&bar"
991 $this->setHTMLTitle(
992 $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
993 ->inContentLanguage()
994 );
995 }
996
1002 public function getPageTitle() {
1003 return $this->mPageTitle;
1004 }
1005
1013 public function setDisplayTitle( $html ) {
1014 $this->displayTitle = $html;
1015 }
1016
1025 public function getDisplayTitle() {
1026 $html = $this->displayTitle;
1027 if ( $html === null ) {
1028 $html = $this->getTitle()->getPrefixedText();
1029 }
1030
1031 return Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $html ) );
1032 }
1033
1040 public function getUnprefixedDisplayTitle() {
1041 $text = $this->getDisplayTitle();
1042 $nsPrefix = $this->getTitle()->getNsText() . ':';
1043 $prefix = preg_quote( $nsPrefix, '/' );
1044
1045 return preg_replace( "/^$prefix/i", '', $text );
1046 }
1047
1053 public function setTitle( Title $t ) {
1054 // @phan-suppress-next-next-line PhanUndeclaredMethod
1055 // @fixme Not all implementations of IContextSource have this method!
1056 $this->getContext()->setTitle( $t );
1057 }
1058
1064 public function setSubtitle( $str ) {
1065 $this->clearSubtitle();
1066 $this->addSubtitle( $str );
1067 }
1068
1074 public function addSubtitle( $str ) {
1075 if ( $str instanceof Message ) {
1076 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1077 } else {
1078 $this->mSubtitle[] = $str;
1079 }
1080 }
1081
1090 public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1091 if ( $title->isRedirect() ) {
1092 $query['redirect'] = 'no';
1093 }
1094 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1095 return wfMessage( 'backlinksubtitle' )
1096 ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1097 }
1098
1105 public function addBacklinkSubtitle( Title $title, $query = [] ) {
1106 $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1107 }
1108
1112 public function clearSubtitle() {
1113 $this->mSubtitle = [];
1114 }
1115
1121 public function getSubtitle() {
1122 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1123 }
1124
1129 public function setPrintable() {
1130 $this->mPrintable = true;
1131 }
1132
1138 public function isPrintable() {
1139 return $this->mPrintable;
1140 }
1141
1145 public function disable() {
1146 $this->mDoNothing = true;
1147 }
1148
1154 public function isDisabled() {
1155 return $this->mDoNothing;
1156 }
1157
1163 public function showNewSectionLink() {
1164 return $this->mNewSectionLink;
1165 }
1166
1172 public function forceHideNewSectionLink() {
1173 return $this->mHideNewSectionLink;
1174 }
1175
1184 public function setSyndicated( $show = true ) {
1185 if ( $show ) {
1186 $this->setFeedAppendQuery( false );
1187 } else {
1188 $this->mFeedLinks = [];
1189 }
1190 }
1191
1198 protected function getAdvertisedFeedTypes() {
1199 if ( $this->getConfig()->get( 'Feed' ) ) {
1200 return $this->getConfig()->get( 'AdvertisedFeedTypes' );
1201 } else {
1202 return [];
1203 }
1204 }
1205
1215 public function setFeedAppendQuery( $val ) {
1216 $this->mFeedLinks = [];
1217
1218 foreach ( $this->getAdvertisedFeedTypes() as $type ) {
1219 $query = "feed=$type";
1220 if ( is_string( $val ) ) {
1221 $query .= '&' . $val;
1222 }
1223 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1224 }
1225 }
1226
1233 public function addFeedLink( $format, $href ) {
1234 if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
1235 $this->mFeedLinks[$format] = $href;
1236 }
1237 }
1238
1243 public function isSyndicated() {
1244 return count( $this->mFeedLinks ) > 0;
1245 }
1246
1251 public function getSyndicationLinks() {
1252 return $this->mFeedLinks;
1253 }
1254
1260 public function getFeedAppendQuery() {
1261 return $this->mFeedLinksAppendQuery;
1262 }
1263
1271 public function setArticleFlag( $newVal ) {
1272 $this->mIsArticle = $newVal;
1273 if ( $newVal ) {
1274 $this->mIsArticleRelated = $newVal;
1275 }
1276 }
1277
1284 public function isArticle() {
1285 return $this->mIsArticle;
1286 }
1287
1294 public function setArticleRelated( $newVal ) {
1295 $this->mIsArticleRelated = $newVal;
1296 if ( !$newVal ) {
1297 $this->mIsArticle = false;
1298 }
1299 }
1300
1306 public function isArticleRelated() {
1307 return $this->mIsArticleRelated;
1308 }
1309
1315 public function setCopyright( $hasCopyright ) {
1316 $this->mHasCopyright = $hasCopyright;
1317 }
1318
1328 public function showsCopyright() {
1329 return $this->isArticle() || $this->mHasCopyright;
1330 }
1331
1338 public function addLanguageLinks( array $newLinkArray ) {
1339 $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $newLinkArray );
1340 }
1341
1348 public function setLanguageLinks( array $newLinkArray ) {
1349 $this->mLanguageLinks = $newLinkArray;
1350 }
1351
1357 public function getLanguageLinks() {
1358 return $this->mLanguageLinks;
1359 }
1360
1366 public function addCategoryLinks( array $categories ) {
1367 if ( !$categories ) {
1368 return;
1369 }
1370
1371 $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1372
1373 # Set all the values to 'normal'.
1374 $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1375
1376 # Mark hidden categories
1377 foreach ( $res as $row ) {
1378 if ( isset( $row->pp_value ) ) {
1379 $categories[$row->page_title] = 'hidden';
1380 }
1381 }
1382
1383 # Add the remaining categories to the skin
1384 if ( $this->getHookRunner()->onOutputPageMakeCategoryLinks(
1385 $this, $categories, $this->mCategoryLinks )
1386 ) {
1387 $services = MediaWikiServices::getInstance();
1388 $linkRenderer = $services->getLinkRenderer();
1389 foreach ( $categories as $category => $type ) {
1390 // array keys will cast numeric category names to ints, so cast back to string
1391 $category = (string)$category;
1392 $origcategory = $category;
1393 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1394 if ( !$title ) {
1395 continue;
1396 }
1397 $services->getContentLanguage()->findVariantLink( $category, $title, true );
1398 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1399 continue;
1400 }
1401 $text = $services->getContentLanguage()->convertHtml( $title->getText() );
1402 $this->mCategories[$type][] = $title->getText();
1403 $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1404 }
1405 }
1406 }
1407
1412 protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1413 # Add the links to a LinkBatch
1414 $arr = [ NS_CATEGORY => $categories ];
1415 $lb = new LinkBatch;
1416 $lb->setArray( $arr );
1417
1418 # Fetch existence plus the hiddencat property
1419 $dbr = wfGetDB( DB_REPLICA );
1420 $fields = array_merge(
1421 LinkCache::getSelectFields(),
1422 [ 'page_namespace', 'page_title', 'pp_value' ]
1423 );
1424
1425 $res = $dbr->select( [ 'page', 'page_props' ],
1426 $fields,
1427 $lb->constructSet( 'page', $dbr ),
1428 __METHOD__,
1429 [],
1430 [ 'page_props' => [ 'LEFT JOIN', [
1431 'pp_propname' => 'hiddencat',
1432 'pp_page = page_id'
1433 ] ] ]
1434 );
1435
1436 # Add the results to the link cache
1437 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1438 $lb->addResultToCache( $linkCache, $res );
1439
1440 return $res;
1441 }
1442
1448 public function setCategoryLinks( array $categories ) {
1449 $this->mCategoryLinks = [];
1450 $this->addCategoryLinks( $categories );
1451 }
1452
1461 public function getCategoryLinks() {
1462 return $this->mCategoryLinks;
1463 }
1464
1474 public function getCategories( $type = 'all' ) {
1475 if ( $type === 'all' ) {
1476 $allCategories = [];
1477 foreach ( $this->mCategories as $categories ) {
1478 $allCategories = array_merge( $allCategories, $categories );
1479 }
1480 return $allCategories;
1481 }
1482 if ( !isset( $this->mCategories[$type] ) ) {
1483 throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1484 }
1485 return $this->mCategories[$type];
1486 }
1487
1497 public function setIndicators( array $indicators ) {
1498 $this->mIndicators = $indicators + $this->mIndicators;
1499 // Keep ordered by key
1500 ksort( $this->mIndicators );
1501 }
1502
1511 public function getIndicators() {
1512 return $this->mIndicators;
1513 }
1514
1523 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1524 $this->addModuleStyles( 'mediawiki.helplink' );
1525 $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1526
1527 if ( $overrideBaseUrl ) {
1528 $helpUrl = $to;
1529 } else {
1530 $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1531 $helpUrl = "https://www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1532 }
1533
1534 $link = Html::rawElement(
1535 'a',
1536 [
1537 'href' => $helpUrl,
1538 'target' => '_blank',
1539 'class' => 'mw-helplink',
1540 ],
1541 $text
1542 );
1543
1544 $this->setIndicators( [ 'mw-helplink' => $link ] );
1545 }
1546
1555 public function disallowUserJs() {
1556 $this->reduceAllowedModules(
1557 ResourceLoaderModule::TYPE_SCRIPTS,
1558 ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
1559 );
1560
1561 // Site-wide styles are controlled by a config setting, see T73621
1562 // for background on why. User styles are never allowed.
1563 if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1564 $styleOrigin = ResourceLoaderModule::ORIGIN_USER_SITEWIDE;
1565 } else {
1566 $styleOrigin = ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL;
1567 }
1568 $this->reduceAllowedModules(
1569 ResourceLoaderModule::TYPE_STYLES,
1570 $styleOrigin
1571 );
1572 }
1573
1580 public function getAllowedModules( $type ) {
1581 if ( $type == ResourceLoaderModule::TYPE_COMBINED ) {
1582 return min( array_values( $this->mAllowedModules ) );
1583 } else {
1584 return $this->mAllowedModules[$type] ?? ResourceLoaderModule::ORIGIN_ALL;
1585 }
1586 }
1587
1597 public function reduceAllowedModules( $type, $level ) {
1598 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1599 }
1600
1606 public function prependHTML( $text ) {
1607 $this->mBodytext = $text . $this->mBodytext;
1608 }
1609
1615 public function addHTML( $text ) {
1616 $this->mBodytext .= $text;
1617 }
1618
1628 public function addElement( $element, array $attribs = [], $contents = '' ) {
1629 $this->addHTML( Html::element( $element, $attribs, $contents ) );
1630 }
1631
1635 public function clearHTML() {
1636 $this->mBodytext = '';
1637 }
1638
1644 public function getHTML() {
1645 return $this->mBodytext;
1646 }
1647
1654 public function parserOptions() {
1655 if ( !$this->mParserOptions ) {
1656 if ( !$this->getUser()->isSafeToLoad() ) {
1657 // $wgUser isn't unstubbable yet, so don't try to get a
1658 // ParserOptions for it. And don't cache this ParserOptions
1659 // either.
1660 $po = ParserOptions::newFromAnon();
1661 $po->setAllowUnsafeRawHtml( false );
1662 $po->isBogus = true;
1663 return $po;
1664 }
1665
1666 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1667 $this->mParserOptions->setAllowUnsafeRawHtml( false );
1668 }
1669
1670 return $this->mParserOptions;
1671 }
1672
1680 public function setRevisionId( $revid ) {
1681 $val = $revid === null ? null : intval( $revid );
1682 return wfSetVar( $this->mRevisionId, $val, true );
1683 }
1684
1690 public function getRevisionId() {
1691 return $this->mRevisionId;
1692 }
1693
1700 public function isRevisionCurrent() {
1701 return $this->mRevisionId == 0 || $this->mRevisionId == $this->getTitle()->getLatestRevID();
1702 }
1703
1711 public function setRevisionTimestamp( $timestamp ) {
1712 return wfSetVar( $this->mRevisionTimestamp, $timestamp, true );
1713 }
1714
1721 public function getRevisionTimestamp() {
1722 return $this->mRevisionTimestamp;
1723 }
1724
1731 public function setFileVersion( $file ) {
1732 $val = null;
1733 if ( $file instanceof File && $file->exists() ) {
1734 $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1735 }
1736 return wfSetVar( $this->mFileVersion, $val, true );
1737 }
1738
1744 public function getFileVersion() {
1745 return $this->mFileVersion;
1746 }
1747
1754 public function getTemplateIds() {
1755 return $this->mTemplateIds;
1756 }
1757
1764 public function getFileSearchOptions() {
1765 return $this->mImageTimeKeys;
1766 }
1767
1784 public function addWikiTextAsInterface(
1785 $text, $linestart = true, Title $title = null
1786 ) {
1787 if ( $title === null ) {
1788 $title = $this->getTitle();
1789 }
1790 if ( !$title ) {
1791 throw new MWException( 'Title is null' );
1792 }
1793 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/true );
1794 }
1795
1810 $wrapperClass, $text
1811 ) {
1813 $text, $this->getTitle(),
1814 /*linestart*/true, /*interface*/true,
1815 $wrapperClass
1816 );
1817 }
1818
1834 public function addWikiTextAsContent(
1835 $text, $linestart = true, Title $title = null
1836 ) {
1837 if ( $title === null ) {
1838 $title = $this->getTitle();
1839 }
1840 if ( !$title ) {
1841 throw new MWException( 'Title is null' );
1842 }
1843 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/false );
1844 }
1845
1859 $text, Title $title, $linestart, $interface, $wrapperClass = null
1860 ) {
1861 $parserOutput = $this->parseInternal(
1862 $text, $title, $linestart, $interface
1863 );
1864
1865 $this->addParserOutput( $parserOutput, [
1866 'enableSectionEditLinks' => false,
1867 'wrapperDivClass' => $wrapperClass ?? '',
1868 ] );
1869 }
1870
1879 public function addParserOutputMetadata( ParserOutput $parserOutput ) {
1880 $this->mLanguageLinks =
1881 array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
1882 $this->addCategoryLinks( $parserOutput->getCategories() );
1883 $this->setIndicators( $parserOutput->getIndicators() );
1884 $this->mNewSectionLink = $parserOutput->getNewSection();
1885 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1886
1887 if ( !$parserOutput->isCacheable() ) {
1888 $this->enableClientCache( false );
1889 }
1890 $this->mNoGallery = $parserOutput->getNoGallery();
1891 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1892 $this->addModules( $parserOutput->getModules() );
1893 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1894 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1895 $this->mPreventClickjacking = $this->mPreventClickjacking
1896 || $parserOutput->preventClickjacking();
1897 $scriptSrcs = $parserOutput->getExtraCSPScriptSrcs();
1898 foreach ( $scriptSrcs as $src ) {
1899 $this->getCSP()->addScriptSrc( $src );
1900 }
1901 $defaultSrcs = $parserOutput->getExtraCSPDefaultSrcs();
1902 foreach ( $defaultSrcs as $src ) {
1903 $this->getCSP()->addDefaultSrc( $src );
1904 }
1905 $styleSrcs = $parserOutput->getExtraCSPStyleSrcs();
1906 foreach ( $styleSrcs as $src ) {
1907 $this->getCSP()->addStyleSrc( $src );
1908 }
1909
1910 // If $wgImagePreconnect is true, and if the output contains
1911 // images, give the user-agent a hint about foreign repos from
1912 // which those images may be served. See T123582.
1913 //
1914 // TODO: We don't have an easy way to know from which remote(s)
1915 // the image(s) will be served. For now, we only hint the first
1916 // valid one.
1917 if ( $this->getConfig()->get( 'ImagePreconnect' ) && count( $parserOutput->getImages() ) ) {
1918 $preconnect = [];
1919 $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
1920 $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$preconnect ) {
1921 $preconnect[] = wfParseUrl( $repo->getZoneUrl( 'thumb' ) )['host'];
1922 } );
1923 $preconnect[] = wfParseUrl( $repoGroup->getLocalRepo()->getZoneUrl( 'thumb' ) )['host'];
1924 foreach ( $preconnect as $host ) {
1925 if ( $host ) {
1926 $this->addLink( [ 'rel' => 'preconnect', 'href' => '//' . $host ] );
1927 break;
1928 }
1929 }
1930 }
1931
1932 // Template versioning...
1933 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1934 if ( isset( $this->mTemplateIds[$ns] ) ) {
1935 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1936 } else {
1937 $this->mTemplateIds[$ns] = $dbks;
1938 }
1939 }
1940 // File versioning...
1941 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1942 $this->mImageTimeKeys[$dbk] = $data;
1943 }
1944
1945 // Hooks registered in the object
1946 $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1947 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1948 list( $hookName, $data ) = $hookInfo;
1949 if ( isset( $parserOutputHooks[$hookName] ) ) {
1950 $parserOutputHooks[$hookName]( $this, $parserOutput, $data );
1951 }
1952 }
1953
1954 // Enable OOUI if requested via ParserOutput
1955 if ( $parserOutput->getEnableOOUI() ) {
1956 $this->enableOOUI();
1957 }
1958
1959 // Include parser limit report
1960 if ( !$this->limitReportJSData ) {
1961 $this->limitReportJSData = $parserOutput->getLimitReportJSData();
1962 }
1963
1964 // Link flags are ignored for now, but may in the future be
1965 // used to mark individual language links.
1966 $linkFlags = [];
1967 $this->getHookRunner()->onLanguageLinks( $this->getTitle(), $this->mLanguageLinks, $linkFlags );
1968 $this->getHookRunner()->onOutputPageParserOutput( $this, $parserOutput );
1969
1970 // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
1971 // so that extensions may modify ParserOutput to toggle TOC.
1972 // This cannot be moved to addParserOutputText because that is not
1973 // called by EditPage for Preview.
1974 if ( $parserOutput->getTOCHTML() ) {
1975 $this->mEnableTOC = true;
1976 }
1977 }
1978
1987 public function addParserOutputContent( ParserOutput $parserOutput, $poOptions = [] ) {
1988 $this->addParserOutputText( $parserOutput, $poOptions );
1989
1990 $this->addModules( $parserOutput->getModules() );
1991 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1992
1993 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1994 }
1995
2003 public function addParserOutputText( ParserOutput $parserOutput, $poOptions = [] ) {
2004 $text = $parserOutput->getText( $poOptions );
2005 $this->getHookRunner()->onOutputPageBeforeHTML( $this, $text );
2006 $this->addHTML( $text );
2007 }
2008
2015 public function addParserOutput( ParserOutput $parserOutput, $poOptions = [] ) {
2016 $this->addParserOutputMetadata( $parserOutput );
2017 $this->addParserOutputText( $parserOutput, $poOptions );
2018 }
2019
2025 public function addTemplate( &$template ) {
2026 $this->addHTML( $template->getHTML() );
2027 }
2028
2040 public function parseAsContent( $text, $linestart = true ) {
2041 return $this->parseInternal(
2042 $text, $this->getTitle(), $linestart, /*interface*/false
2043 )->getText( [
2044 'enableSectionEditLinks' => false,
2045 'wrapperDivClass' => ''
2046 ] );
2047 }
2048
2061 public function parseAsInterface( $text, $linestart = true ) {
2062 return $this->parseInternal(
2063 $text, $this->getTitle(), $linestart, /*interface*/true
2064 )->getText( [
2065 'enableSectionEditLinks' => false,
2066 'wrapperDivClass' => ''
2067 ] );
2068 }
2069
2084 public function parseInlineAsInterface( $text, $linestart = true ) {
2085 return Parser::stripOuterParagraph(
2086 $this->parseAsInterface( $text, $linestart )
2087 );
2088 }
2089
2102 private function parseInternal( $text, $title, $linestart, $interface ) {
2103 if ( $title === null ) {
2104 throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
2105 }
2106
2107 $popts = $this->parserOptions();
2108
2109 $oldInterface = $popts->setInterfaceMessage( (bool)$interface );
2110
2111 $parserOutput = MediaWikiServices::getInstance()->getParser()->getFreshParser()->parse(
2112 $text, $title, $popts,
2113 $linestart, true, $this->mRevisionId
2114 );
2115
2116 $popts->setInterfaceMessage( $oldInterface );
2117
2118 return $parserOutput;
2119 }
2120
2126 public function setCdnMaxage( $maxage ) {
2127 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2128 }
2129
2139 public function lowerCdnMaxage( $maxage ) {
2140 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2141 $this->setCdnMaxage( $this->mCdnMaxage );
2142 }
2143
2156 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2157 $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2158 $maxTTL = $maxTTL ?: $this->getConfig()->get( 'CdnMaxAge' );
2159
2160 if ( $mtime === null || $mtime === false ) {
2161 return; // entity does not exist
2162 }
2163
2164 $age = MWTimestamp::time() - (int)wfTimestamp( TS_UNIX, $mtime );
2165 $adaptiveTTL = max( 0.9 * $age, $minTTL );
2166 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2167
2168 $this->lowerCdnMaxage( (int)$adaptiveTTL );
2169 }
2170
2178 public function enableClientCache( $state ) {
2179 return wfSetVar( $this->mEnableClientCache, $state );
2180 }
2181
2188 public function couldBePublicCached() {
2189 if ( !$this->cacheIsFinal ) {
2190 // - The entry point handles its own caching and/or doesn't use OutputPage.
2191 // (such as load.php, AjaxDispatcher, or MediaWiki\Rest\EntryPoint).
2192 //
2193 // - Or, we haven't finished processing the main part of the request yet
2194 // (e.g. Action::show, SpecialPage::execute), and the state may still
2195 // change via enableClientCache().
2196 return true;
2197 }
2198 // e.g. various error-type pages disable all client caching
2199 return $this->mEnableClientCache;
2200 }
2201
2211 public function considerCacheSettingsFinal() {
2212 $this->cacheIsFinal = true;
2213 }
2214
2220 public function getCacheVaryCookies() {
2221 if ( self::$cacheVaryCookies === null ) {
2222 $config = $this->getConfig();
2223 self::$cacheVaryCookies = array_values( array_unique( array_merge(
2224 SessionManager::singleton()->getVaryCookies(),
2225 [
2226 'forceHTTPS',
2227 ],
2228 $config->get( 'CacheVaryCookies' )
2229 ) ) );
2230 $this->getHookRunner()->onGetCacheVaryCookies( $this, self::$cacheVaryCookies );
2231 }
2232 return self::$cacheVaryCookies;
2233 }
2234
2241 public function haveCacheVaryCookies() {
2242 $request = $this->getRequest();
2243 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2244 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2245 wfDebug( __METHOD__ . ": found $cookieName" );
2246 return true;
2247 }
2248 }
2249 wfDebug( __METHOD__ . ": no cache-varying cookies found" );
2250 return false;
2251 }
2252
2262 public function addVaryHeader( $header, array $option = null ) {
2263 if ( $option !== null && count( $option ) > 0 ) {
2265 'The $option parameter to addVaryHeader is ignored since MediaWiki 1.34',
2266 '1.34' );
2267 }
2268 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2269 $this->mVaryHeader[$header] = null;
2270 }
2271 }
2272
2279 public function getVaryHeader() {
2280 // If we vary on cookies, let's make sure it's always included here too.
2281 if ( $this->getCacheVaryCookies() ) {
2282 $this->addVaryHeader( 'Cookie' );
2283 }
2284
2285 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2286 $this->addVaryHeader( $header, $options );
2287 }
2288 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2289 }
2290
2296 public function addLinkHeader( $header ) {
2297 $this->mLinkHeader[] = $header;
2298 }
2299
2305 public function getLinkHeader() {
2306 if ( !$this->mLinkHeader ) {
2307 return false;
2308 }
2309
2310 return 'Link: ' . implode( ',', $this->mLinkHeader );
2311 }
2312
2320 private function addAcceptLanguage() {
2321 $title = $this->getTitle();
2322 if ( !$title instanceof Title ) {
2323 return;
2324 }
2325
2326 $lang = $title->getPageLanguage();
2327 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2328 $this->addVaryHeader( 'Accept-Language' );
2329 }
2330 }
2331
2342 public function preventClickjacking( $enable = true ) {
2343 $this->mPreventClickjacking = $enable;
2344 }
2345
2351 public function allowClickjacking() {
2352 $this->mPreventClickjacking = false;
2353 }
2354
2361 public function getPreventClickjacking() {
2362 return $this->mPreventClickjacking;
2363 }
2364
2372 public function getFrameOptions() {
2373 $config = $this->getConfig();
2374 if ( $config->get( 'BreakFrames' ) ) {
2375 return 'DENY';
2376 } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2377 return $config->get( 'EditPageFrameOptions' );
2378 }
2379 return false;
2380 }
2381
2388 private function getOriginTrials() {
2389 $config = $this->getConfig();
2390
2391 return $config->get( 'OriginTrials' );
2392 }
2393
2394 private function getReportTo() {
2395 $config = $this->getConfig();
2396
2397 $expiry = $config->get( 'ReportToExpiry' );
2398
2399 if ( !$expiry ) {
2400 return false;
2401 }
2402
2403 $endpoints = $config->get( 'ReportToEndpoints' );
2404
2405 if ( !$endpoints ) {
2406 return false;
2407 }
2408
2409 $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
2410
2411 foreach ( $endpoints as $endpoint ) {
2412 $output['endpoints'][] = [ 'url' => $endpoint ];
2413 }
2414
2415 return json_encode( $output, JSON_UNESCAPED_SLASHES );
2416 }
2417
2418 private function getFeaturePolicyReportOnly() {
2419 $config = $this->getConfig();
2420
2421 $features = $config->get( 'FeaturePolicyReportOnly' );
2422 return implode( ';', $features );
2423 }
2424
2428 public function sendCacheControl() {
2429 $response = $this->getRequest()->response();
2430 $config = $this->getConfig();
2431
2432 $this->addVaryHeader( 'Cookie' );
2433 $this->addAcceptLanguage();
2434
2435 # don't serve compressed data to clients who can't handle it
2436 # maintain different caches for logged-in users and non-logged in ones
2437 $response->header( $this->getVaryHeader() );
2438
2439 if ( $this->mEnableClientCache ) {
2440 if (
2441 $config->get( 'UseCdn' ) &&
2442 !$response->hasCookies() &&
2443 // The client might use methods other than cookies to appear logged-in.
2444 // E.g. HTTP headers, or query parameter tokens, OAuth, etc.
2445 !SessionManager::getGlobalSession()->isPersistent() &&
2446 !$this->isPrintable() &&
2447 $this->mCdnMaxage != 0 &&
2448 !$this->haveCacheVaryCookies()
2449 ) {
2450 # We'll purge the proxy cache for anons explicitly, but require end user agents
2451 # to revalidate against the proxy on each visit.
2452 # IMPORTANT! The CDN needs to replace the Cache-Control header with
2453 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2454 wfDebug( __METHOD__ .
2455 ": local proxy caching; {$this->mLastModified} **", 'private' );
2456 # start with a shorter timeout for initial testing
2457 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2458 $response->header( "Cache-Control: " .
2459 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2460 } else {
2461 # We do want clients to cache if they can, but they *must* check for updates
2462 # on revisiting the page, after the max-age period.
2463 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2464
2465 if ( $response->hasCookies() || SessionManager::getGlobalSession()->isPersistent() ) {
2466 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2467 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2468 } else {
2469 $response->header(
2470 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $config->get( 'LoggedOutMaxAge' ) ) . ' GMT'
2471 );
2472 $response->header(
2473 "Cache-Control: private, must-revalidate, max-age={$config->get( 'LoggedOutMaxAge' )}"
2474 );
2475 }
2476 }
2477 if ( $this->mLastModified ) {
2478 $response->header( "Last-Modified: {$this->mLastModified}" );
2479 }
2480 } else {
2481 wfDebug( __METHOD__ . ": no caching **", 'private' );
2482
2483 # In general, the absence of a last modified header should be enough to prevent
2484 # the client from using its cache. We send a few other things just to make sure.
2485 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2486 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2487 $response->header( 'Pragma: no-cache' );
2488 }
2489 }
2490
2496 public function loadSkinModules( $sk ) {
2497 foreach ( $sk->getDefaultModules() as $group => $modules ) {
2498 if ( $group === 'styles' ) {
2499 foreach ( $modules as $key => $moduleMembers ) {
2500 $this->addModuleStyles( $moduleMembers );
2501 }
2502 } else {
2503 $this->addModules( $modules );
2504 }
2505 }
2506 }
2507
2518 public function output( $return = false ) {
2519 if ( $this->mDoNothing ) {
2520 return $return ? '' : null;
2521 }
2522
2523 $response = $this->getRequest()->response();
2524 $config = $this->getConfig();
2525
2526 if ( $this->mRedirect != '' ) {
2527 # Standards require redirect URLs to be absolute
2528 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2529
2530 $redirect = $this->mRedirect;
2531 $code = $this->mRedirectCode;
2532 $content = '';
2533
2534 if ( $this->getHookRunner()->onBeforePageRedirect( $this, $redirect, $code ) ) {
2535 if ( $code == '301' || $code == '303' ) {
2536 if ( !$config->get( 'DebugRedirects' ) ) {
2537 $response->statusHeader( $code );
2538 }
2539 $this->mLastModified = wfTimestamp( TS_RFC2822 );
2540 }
2541 if ( $config->get( 'VaryOnXFP' ) ) {
2542 $this->addVaryHeader( 'X-Forwarded-Proto' );
2543 }
2544 $this->sendCacheControl();
2545
2546 $response->header( "Content-Type: text/html; charset=utf-8" );
2547 if ( $config->get( 'DebugRedirects' ) ) {
2548 $url = htmlspecialchars( $redirect );
2549 $content = "<!DOCTYPE html>\n<html>\n<head>\n"
2550 . "<title>Redirect</title>\n</head>\n<body>\n"
2551 . "<p>Location: <a href=\"$url\">$url</a></p>\n"
2552 . "</body>\n</html>\n";
2553
2554 if ( !$return ) {
2556 }
2557
2558 } else {
2559 $response->header( 'Location: ' . $redirect );
2560 }
2561 }
2562
2563 return $return ? $content : null;
2564 } elseif ( $this->mStatusCode ) {
2565 $response->statusHeader( $this->mStatusCode );
2566 }
2567
2568 # Buffer output; final headers may depend on later processing
2569 ob_start();
2570
2571 $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2572 $response->header( 'Content-language: ' .
2573 MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
2574
2575 $linkHeader = $this->getLinkHeader();
2576 if ( $linkHeader ) {
2577 $response->header( $linkHeader );
2578 }
2579
2580 // Prevent framing, if requested
2581 $frameOptions = $this->getFrameOptions();
2582 if ( $frameOptions ) {
2583 $response->header( "X-Frame-Options: $frameOptions" );
2584 }
2585
2586 $originTrials = $this->getOriginTrials();
2587 foreach ( $originTrials as $originTrial ) {
2588 $response->header( "Origin-Trial: $originTrial", false );
2589 }
2590
2591 $reportTo = $this->getReportTo();
2592 if ( $reportTo ) {
2593 $response->header( "Report-To: $reportTo" );
2594 }
2595
2596 $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
2597 if ( $featurePolicyReportOnly ) {
2598 $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
2599 }
2600
2601 if ( $this->mArticleBodyOnly ) {
2602 $this->CSP->sendHeaders();
2603 echo $this->mBodytext;
2604 } else {
2605 // Enable safe mode if requested (T152169)
2606 if ( $this->getRequest()->getBool( 'safemode' ) ) {
2607 $this->disallowUserJs();
2608 }
2609
2610 $sk = $this->getSkin();
2611 $this->loadSkinModules( $sk );
2612
2613 MWDebug::addModules( $this );
2614
2615 // Hook that allows last minute changes to the output page, e.g.
2616 // adding of CSS or Javascript by extensions, adding CSP sources.
2617 $this->getHookRunner()->onBeforePageDisplay( $this, $sk );
2618
2619 $this->CSP->sendHeaders();
2620
2621 try {
2622 $sk->outputPage();
2623 } catch ( Exception $e ) {
2624 ob_end_clean(); // bug T129657
2625 throw $e;
2626 }
2627 }
2628
2629 try {
2630 // This hook allows last minute changes to final overall output by modifying output buffer
2631 $this->getHookRunner()->onAfterFinalPageOutput( $this );
2632 } catch ( Exception $e ) {
2633 ob_end_clean(); // bug T129657
2634 throw $e;
2635 }
2636
2637 $this->sendCacheControl();
2638
2639 if ( $return ) {
2640 return ob_get_clean();
2641 } else {
2642 ob_end_flush();
2643 return null;
2644 }
2645 }
2646
2657 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2658 $this->setPageTitle( $pageTitle );
2659 if ( $htmlTitle !== false ) {
2660 $this->setHTMLTitle( $htmlTitle );
2661 }
2662 $this->setRobotPolicy( 'noindex,nofollow' );
2663 $this->setArticleRelated( false );
2664 $this->enableClientCache( false );
2665 $this->mRedirect = '';
2666 $this->clearSubtitle();
2667 $this->clearHTML();
2668 }
2669
2682 public function showErrorPage( $title, $msg, $params = [] ) {
2683 if ( !$title instanceof Message ) {
2684 $title = $this->msg( $title );
2685 }
2686
2687 $this->prepareErrorPage( $title );
2688
2689 if ( $msg instanceof Message ) {
2690 if ( $params !== [] ) {
2691 trigger_error( 'Argument ignored: $params. The message parameters argument '
2692 . 'is discarded when the $msg argument is a Message object instead of '
2693 . 'a string.', E_USER_NOTICE );
2694 }
2695 $this->addHTML( $msg->parseAsBlock() );
2696 } else {
2697 $this->addWikiMsgArray( $msg, $params );
2698 }
2699
2700 $this->returnToMain();
2701 }
2702
2709 public function showPermissionsErrorPage( array $errors, $action = null ) {
2710 $services = MediaWikiServices::getInstance();
2711 $permissionManager = $services->getPermissionManager();
2712 foreach ( $errors as $key => $error ) {
2713 $errors[$key] = (array)$error;
2714 }
2715
2716 // For some action (read, edit, create and upload), display a "login to do this action"
2717 // error if all of the following conditions are met:
2718 // 1. the user is not logged in
2719 // 2. the only error is insufficient permissions (i.e. no block or something else)
2720 // 3. the error can be avoided simply by logging in
2721
2722 if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2723 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2724 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2725 && ( $permissionManager->groupHasPermission( 'user', $action )
2726 || $permissionManager->groupHasPermission( 'autoconfirmed', $action ) )
2727 ) {
2728 $displayReturnto = null;
2729
2730 # Due to T34276, if a user does not have read permissions,
2731 # $this->getTitle() will just give Special:Badtitle, which is
2732 # not especially useful as a returnto parameter. Use the title
2733 # from the request instead, if there was one.
2734 $request = $this->getRequest();
2735 $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2736 if ( $action == 'edit' ) {
2737 $msg = 'whitelistedittext';
2738 $displayReturnto = $returnto;
2739 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2740 $msg = 'nocreatetext';
2741 } elseif ( $action == 'upload' ) {
2742 $msg = 'uploadnologintext';
2743 } else { # Read
2744 $msg = 'loginreqpagetext';
2745 $displayReturnto = Title::newMainPage();
2746 }
2747
2748 $query = [];
2749
2750 if ( $returnto ) {
2751 $query['returnto'] = $returnto->getPrefixedText();
2752
2753 if ( !$request->wasPosted() ) {
2754 $returntoquery = $request->getValues();
2755 unset( $returntoquery['title'] );
2756 unset( $returntoquery['returnto'] );
2757 unset( $returntoquery['returntoquery'] );
2758 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2759 }
2760 }
2761
2762 $title = SpecialPage::getTitleFor( 'Userlogin' );
2763 $linkRenderer = $services->getLinkRenderer();
2764 $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
2765 $loginLink = $linkRenderer->makeKnownLink(
2766 $title,
2767 $this->msg( 'loginreqlink' )->text(),
2768 [],
2769 $query
2770 );
2771
2772 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2773 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
2774
2775 # Don't return to a page the user can't read otherwise
2776 # we'll end up in a pointless loop
2777 if ( $displayReturnto && $permissionManager->userCan(
2778 'read', $this->getUser(), $displayReturnto
2779 ) ) {
2780 $this->returnToMain( null, $displayReturnto );
2781 }
2782 } else {
2783 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2784 $this->addWikiTextAsInterface( $this->formatPermissionsErrorMessage( $errors, $action ) );
2785 }
2786 }
2787
2794 public function versionRequired( $version ) {
2795 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2796
2797 $this->addWikiMsg( 'versionrequiredtext', $version );
2798 $this->returnToMain();
2799 }
2800
2808 public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2809 if ( $action == null ) {
2810 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2811 } else {
2812 $action_desc = $this->msg( "action-$action" )->plain();
2813 $text = $this->msg(
2814 'permissionserrorstext-withaction',
2815 count( $errors ),
2816 $action_desc
2817 )->plain() . "\n\n";
2818 }
2819
2820 if ( count( $errors ) > 1 ) {
2821 $text .= '<ul class="permissions-errors">' . "\n";
2822
2823 foreach ( $errors as $error ) {
2824 $text .= '<li>';
2825 $text .= $this->msg( ...$error )->plain();
2826 $text .= "</li>\n";
2827 }
2828 $text .= '</ul>';
2829 } else {
2830 $text .= "<div class=\"permissions-errors\">\n" .
2831 $this->msg( ...reset( $errors ) )->plain() .
2832 "\n</div>";
2833 }
2834
2835 return $text;
2836 }
2837
2847 public function showLagWarning( $lag ) {
2848 $config = $this->getConfig();
2849 if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2850 $lag = floor( $lag ); // floor to avoid nano seconds to display
2851 $message = $lag < $config->get( 'SlaveLagCritical' )
2852 ? 'lag-warn-normal'
2853 : 'lag-warn-high';
2854 // For grep: mw-lag-warn-normal, mw-lag-warn-high
2855 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2856 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2857 }
2858 }
2859
2866 public function showFatalError( $message ) {
2867 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2868
2869 $this->addHTML( $message );
2870 }
2871
2880 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2881 $linkRenderer = MediaWikiServices::getInstance()
2882 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
2883 $link = $this->msg( 'returnto' )->rawParams(
2884 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
2885 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2886 }
2887
2896 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2897 if ( $returnto == null ) {
2898 $returnto = $this->getRequest()->getText( 'returnto' );
2899 }
2900
2901 if ( $returntoquery == null ) {
2902 $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2903 }
2904
2905 if ( $returnto === '' ) {
2906 $returnto = Title::newMainPage();
2907 }
2908
2909 if ( is_object( $returnto ) ) {
2910 $titleObj = $returnto;
2911 } else {
2912 $titleObj = Title::newFromText( $returnto );
2913 }
2914 // We don't want people to return to external interwiki. That
2915 // might potentially be used as part of a phishing scheme
2916 if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
2917 $titleObj = Title::newMainPage();
2918 }
2919
2920 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2921 }
2922
2923 private function getRlClientContext() {
2924 if ( !$this->rlClientContext ) {
2926 [], // modules; not relevant
2927 $this->getLanguage()->getCode(),
2928 $this->getSkin()->getSkinName(),
2929 $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
2930 null, // version; not relevant
2932 null, // only; not relevant
2933 $this->isPrintable(),
2934 $this->getRequest()->getBool( 'handheld' )
2935 );
2936 $this->rlClientContext = new ResourceLoaderContext(
2937 $this->getResourceLoader(),
2938 new FauxRequest( $query )
2939 );
2940 if ( $this->contentOverrideCallbacks ) {
2941 $this->rlClientContext = new DerivativeResourceLoaderContext( $this->rlClientContext );
2942 $this->rlClientContext->setContentOverrideCallback( function ( Title $title ) {
2943 foreach ( $this->contentOverrideCallbacks as $callback ) {
2944 $content = $callback( $title );
2945 if ( $content !== null ) {
2946 $text = ContentHandler::getContentText( $content );
2947 if ( strpos( $text, '</script>' ) !== false ) {
2948 // Proactively replace this so that we can display a message
2949 // to the user, instead of letting it go to Html::inlineScript(),
2950 // where it would be considered a server-side issue.
2951 $titleFormatted = $title->getPrefixedText();
2953 Xml::encodeJsCall( 'mw.log.error', [
2954 "Cannot preview $titleFormatted due to script-closing tag."
2955 ] )
2956 );
2957 }
2958 return $content;
2959 }
2960 }
2961 return null;
2962 } );
2963 }
2964 }
2965 return $this->rlClientContext;
2966 }
2967
2979 public function getRlClient() {
2980 if ( !$this->rlClient ) {
2981 $context = $this->getRlClientContext();
2982 $rl = $this->getResourceLoader();
2983 $this->addModules( [
2984 'user',
2985 'user.options',
2986 ] );
2987 $this->addModuleStyles( [
2988 'site.styles',
2989 'noscript',
2990 'user.styles',
2991 ] );
2992 $this->getSkin()->setupSkinUserCss( $this );
2993
2994 // Prepare exempt modules for buildExemptModules()
2995 $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
2996 $exemptStates = [];
2997 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
2998
2999 // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
3000 // Separate user-specific batch for improved cache-hit ratio.
3001 $userBatch = [ 'user.styles', 'user' ];
3002 $siteBatch = array_diff( $moduleStyles, $userBatch );
3003 $dbr = wfGetDB( DB_REPLICA );
3004 ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $siteBatch );
3005 ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $userBatch );
3006
3007 // Filter out modules handled by buildExemptModules()
3008 $moduleStyles = array_filter( $moduleStyles,
3009 function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3010 $module = $rl->getModule( $name );
3011 if ( $module ) {
3012 $group = $module->getGroup();
3013 if ( isset( $exemptGroups[$group] ) ) {
3014 $exemptStates[$name] = 'ready';
3015 if ( !$module->isKnownEmpty( $context ) ) {
3016 // E.g. Don't output empty <styles>
3017 $exemptGroups[$group][] = $name;
3018 }
3019 return false;
3020 }
3021 }
3022 return true;
3023 }
3024 );
3025 $this->rlExemptStyleModules = $exemptGroups;
3026
3028 'target' => $this->getTarget(),
3029 'nonce' => $this->CSP->getNonce(),
3030 // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3031 // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3032 // modules enqueud for loading on this page is filtered to just those.
3033 // However, to make sure we also apply the restriction to dynamic dependencies and
3034 // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3035 // StartupModule so that the client-side registry will not contain any restricted
3036 // modules either. (T152169, T185303)
3037 'safemode' => ( $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
3038 <= ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
3039 ) ? '1' : null,
3040 ] );
3041 $rlClient->setConfig( $this->getJSVars() );
3042 $rlClient->setModules( $this->getModules( /*filter*/ true ) );
3043 $rlClient->setModuleStyles( $moduleStyles );
3044 $rlClient->setExemptStates( $exemptStates );
3045 $this->rlClient = $rlClient;
3046 }
3047 return $this->rlClient;
3048 }
3049
3055 public function headElement( Skin $sk, $includeStyle = true ) {
3056 $config = $this->getConfig();
3057 $userdir = $this->getLanguage()->getDir();
3058 $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
3059
3060 $pieces = [];
3061 $htmlAttribs = Sanitizer::mergeAttributes( Sanitizer::mergeAttributes(
3062 $this->getRlClient()->getDocumentAttributes(),
3064 ), [ 'class' => implode( ' ', $this->mAdditionalHtmlClasses ) ] );
3065 $pieces[] = Html::htmlHeader( $htmlAttribs );
3066 $pieces[] = Html::openElement( 'head' );
3067
3068 if ( $this->getHTMLTitle() == '' ) {
3069 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3070 }
3071
3072 if ( !Html::isXmlMimeType( $config->get( 'MimeType' ) ) ) {
3073 // Add <meta charset="UTF-8">
3074 // This should be before <title> since it defines the charset used by
3075 // text including the text inside <title>.
3076 // The spec recommends defining XHTML5's charset using the XML declaration
3077 // instead of meta.
3078 // Our XML declaration is output by Html::htmlHeader.
3079 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3080 // https://html.spec.whatwg.org/multipage/semantics.html#charset
3081 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3082 }
3083
3084 $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
3085 $pieces[] = $this->getRlClient()->getHeadHtml( $htmlAttribs['class'] ?? null );
3086 $pieces[] = $this->buildExemptModules();
3087 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3088 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3089
3090 // This library is intended to run on older browsers that MediaWiki no longer
3091 // supports as Grade A. For these Grade C browsers, we provide an experience
3092 // using only HTML and CSS. But, where standards-compliant browsers are able to
3093 // style unknown HTML elements without issue, old IE ignores these styles.
3094 // The html5shiv library fixes that.
3095 // Use an IE conditional comment to serve the script only to old IE
3096 $shivUrl = $config->get( 'ResourceBasePath' ) . '/resources/lib/html5shiv/html5shiv.js';
3097 $pieces[] = '<!--[if lt IE 9]>' .
3098 Html::linkedScript( $shivUrl, $this->CSP->getNonce() ) .
3099 '<![endif]-->';
3100
3101 $pieces[] = Html::closeElement( 'head' );
3102
3103 $bodyClasses = $this->mAdditionalBodyClasses;
3104 $bodyClasses[] = 'mediawiki';
3105
3106 # Classes for LTR/RTL directionality support
3107 $bodyClasses[] = $userdir;
3108 $bodyClasses[] = "sitedir-$sitedir";
3109
3110 $underline = $this->getUser()->getOption( 'underline' );
3111 if ( $underline < 2 ) {
3112 // The following classes can be used here:
3113 // * mw-underline-always
3114 // * mw-underline-never
3115 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3116 }
3117
3118 if ( $this->getLanguage()->capitalizeAllNouns() ) {
3119 # A <body> class is probably not the best way to do this . . .
3120 $bodyClasses[] = 'capitalize-all-nouns';
3121 }
3122
3123 // Parser feature migration class
3124 // The idea is that this will eventually be removed, after the wikitext
3125 // which requires it is cleaned up.
3126 $bodyClasses[] = 'mw-hide-empty-elt';
3127
3128 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3129 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3130 $bodyClasses[] =
3131 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3132
3133 $bodyAttrs = [];
3134 // While the implode() is not strictly needed, it's used for backwards compatibility
3135 // (this used to be built as a string and hooks likely still expect that).
3136 $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3137
3138 // Allow skins and extensions to add body attributes they need
3139 // Get ones from deprecated method
3140 if ( method_exists( $sk, 'addToBodyAttributes' ) ) {
3142 $sk->addToBodyAttributes( $this, $bodyAttrs );
3143 wfDeprecated( 'Skin::addToBodyAttributes method to add body attributes', '1.35' );
3144 }
3145
3146 // Then run the hook, the recommended way of adding body attributes now
3147 $this->getHookRunner()->onOutputPageBodyAttributes( $this, $sk, $bodyAttrs );
3148
3149 $pieces[] = Html::openElement( 'body', $bodyAttrs );
3150
3151 return self::combineWrappedStrings( $pieces );
3152 }
3153
3159 public function getResourceLoader() {
3160 if ( $this->mResourceLoader === null ) {
3161 // Lazy-initialise as needed
3162 $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3163 }
3164 return $this->mResourceLoader;
3165 }
3166
3175 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3176 // Apply 'target' and 'origin' filters
3177 $modules = $this->filterModules( (array)$modules, null, $only );
3178
3180 $this->getRlClientContext(),
3181 $modules,
3182 $only,
3183 $extraQuery,
3184 $this->CSP->getNonce()
3185 );
3186 }
3187
3194 protected static function combineWrappedStrings( array $chunks ) {
3195 // Filter out empty values
3196 $chunks = array_filter( $chunks, 'strlen' );
3197 return WrappedString::join( "\n", $chunks );
3198 }
3199
3206 public function getBottomScripts() {
3207 $chunks = [];
3208 $chunks[] = $this->getRlClient()->getBodyHtml();
3209
3210 // Legacy non-ResourceLoader scripts
3211 $chunks[] = $this->mScripts;
3212
3213 if ( $this->limitReportJSData ) {
3216 [ 'wgPageParseReport' => $this->limitReportJSData ]
3217 ),
3218 $this->CSP->getNonce()
3219 );
3220 }
3221
3222 return self::combineWrappedStrings( $chunks );
3223 }
3224
3231 public function getJsConfigVars() {
3232 return $this->mJsConfigVars;
3233 }
3234
3241 public function addJsConfigVars( $keys, $value = null ) {
3242 if ( is_array( $keys ) ) {
3243 foreach ( $keys as $key => $value ) {
3244 $this->mJsConfigVars[$key] = $value;
3245 }
3246 return;
3247 }
3248
3249 $this->mJsConfigVars[$keys] = $value;
3250 }
3251
3261 public function getJSVars() {
3262 $curRevisionId = 0;
3263 $articleId = 0;
3264 $canonicalSpecialPageName = false; # T23115
3265 $services = MediaWikiServices::getInstance();
3266
3267 $title = $this->getTitle();
3268 $ns = $title->getNamespace();
3269 $nsInfo = $services->getNamespaceInfo();
3270 $canonicalNamespace = $nsInfo->exists( $ns )
3271 ? $nsInfo->getCanonicalName( $ns )
3272 : $title->getNsText();
3273
3274 $sk = $this->getSkin();
3275 // Get the relevant title so that AJAX features can use the correct page name
3276 // when making API requests from certain special pages (T36972).
3277 $relevantTitle = $sk->getRelevantTitle();
3278 $relevantUser = $sk->getRelevantUser();
3279
3280 if ( $ns == NS_SPECIAL ) {
3281 list( $canonicalSpecialPageName, /*...*/ ) =
3282 $services->getSpecialPageFactory()->
3283 resolveAlias( $title->getDBkey() );
3284 } elseif ( $this->canUseWikiPage() ) {
3285 $wikiPage = $this->getWikiPage();
3286 $curRevisionId = $wikiPage->getLatest();
3287 $articleId = $wikiPage->getId();
3288 }
3289
3290 $lang = $title->getPageViewLanguage();
3291
3292 // Pre-process information
3293 $separatorTransTable = $lang->separatorTransformTable();
3294 $separatorTransTable = $separatorTransTable ?: [];
3295 $compactSeparatorTransTable = [
3296 implode( "\t", array_keys( $separatorTransTable ) ),
3297 implode( "\t", $separatorTransTable ),
3298 ];
3299 $digitTransTable = $lang->digitTransformTable();
3300 $digitTransTable = $digitTransTable ?: [];
3301 $compactDigitTransTable = [
3302 implode( "\t", array_keys( $digitTransTable ) ),
3303 implode( "\t", $digitTransTable ),
3304 ];
3305
3306 $user = $this->getUser();
3307
3308 // Internal variables for MediaWiki core
3309 $vars = [
3310 // @internal For mediawiki.page.startup
3311 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3312
3313 // @internal For jquery.tablesorter
3314 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3315 'wgDigitTransformTable' => $compactDigitTransTable,
3316 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3317 'wgMonthNames' => $lang->getMonthNamesArray(),
3318
3319 // @internal For debugging purposes
3320 'wgRequestId' => WebRequest::getRequestId(),
3321
3322 // @internal For mw.loader
3323 'wgCSPNonce' => $this->CSP->getNonce(),
3324 ];
3325
3326 // Start of supported and stable config vars (for use by extensions/gadgets).
3327 $vars += [
3328 'wgCanonicalNamespace' => $canonicalNamespace,
3329 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3330 'wgNamespaceNumber' => $title->getNamespace(),
3331 'wgPageName' => $title->getPrefixedDBkey(),
3332 'wgTitle' => $title->getText(),
3333 'wgCurRevisionId' => $curRevisionId,
3334 'wgRevisionId' => (int)$this->getRevisionId(),
3335 'wgArticleId' => $articleId,
3336 'wgIsArticle' => $this->isArticle(),
3337 'wgIsRedirect' => $title->isRedirect(),
3338 'wgAction' => Action::getActionName( $this->getContext() ),
3339 'wgUserName' => $user->isAnon() ? null : $user->getName(),
3340 'wgUserGroups' => $user->getEffectiveGroups(),
3341 'wgCategories' => $this->getCategories(),
3342 'wgPageContentLanguage' => $lang->getCode(),
3343 'wgPageContentModel' => $title->getContentModel(),
3344 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3345 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3346 ];
3347 if ( $user->isLoggedIn() ) {
3348 $vars['wgUserId'] = $user->getId();
3349 $vars['wgUserEditCount'] = $user->getEditCount();
3350 $userReg = $user->getRegistration();
3351 $vars['wgUserRegistration'] = $userReg ? (int)wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3352 // Get the revision ID of the oldest new message on the user's talk
3353 // page. This can be used for constructing new message alerts on
3354 // the client side.
3355 $userNewMsgRevId = $this->getLastSeenUserTalkRevId();
3356 // Only occupy precious space in the <head> when it is non-null (T53640)
3357 // mw.config.get returns null by default.
3358 if ( $userNewMsgRevId ) {
3359 $vars['wgUserNewMsgRevisionId'] = $userNewMsgRevId;
3360 }
3361 }
3362 $contLang = $services->getContentLanguage();
3363 if ( $contLang->hasVariants() ) {
3364 $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3365 }
3366 // Same test as SkinTemplate
3367 $vars['wgIsProbablyEditable'] = $this->userCanEditOrCreate( $user, $title );
3368 $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle &&
3369 $this->userCanEditOrCreate( $user, $relevantTitle );
3370 foreach ( $title->getRestrictionTypes() as $type ) {
3371 // Following keys are set in $vars:
3372 // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3373 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3374 }
3375 if ( $title->isMainPage() ) {
3376 $vars['wgIsMainPage'] = true;
3377 }
3378 if ( $relevantUser && ( !$relevantUser->isHidden() ||
3379 $services->getPermissionManager()->userHasRight( $user, 'hideuser' ) )
3380 ) {
3381 // T120883 if the user is hidden and the viewer cannot see
3382 // hidden users, pretend like it does not exist at all.
3383 $vars['wgRelevantUserName'] = $relevantUser->getName();
3384 }
3385 // End of stable config vars
3386
3387 if ( $this->mRedirectedFrom ) {
3388 // @internal For skin JS
3389 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3390 }
3391
3392 // Allow extensions to add their custom variables to the mw.config map.
3393 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3394 // page-dependant but site-wide (without state).
3395 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3396 $this->getHookRunner()->onMakeGlobalVariablesScript( $vars, $this );
3397
3398 // Merge in variables from addJsConfigVars last
3399 return array_merge( $vars, $this->getJsConfigVars() );
3400 }
3401
3407 private function getLastSeenUserTalkRevId() {
3408 $services = MediaWikiServices::getInstance();
3409 $user = $this->getUser();
3410 $userHasNewMessages = $services
3411 ->getTalkPageNotificationManager()
3412 ->userHasNewMessages( $user );
3413 if ( !$userHasNewMessages ) {
3414 return null;
3415 }
3416
3417 $timestamp = $services
3418 ->getTalkPageNotificationManager()
3419 ->getLatestSeenMessageTimestamp( $user );
3420
3421 if ( !$timestamp ) {
3422 return null;
3423 }
3424
3425 $revRecord = $services->getRevisionLookup()->getRevisionByTimestamp(
3426 $user->getTalkPage(),
3427 $timestamp
3428 );
3429
3430 if ( !$revRecord ) {
3431 return null;
3432 }
3433
3434 return $revRecord->getId();
3435 }
3436
3446 public function userCanPreview() {
3447 $request = $this->getRequest();
3448 if (
3449 $request->getVal( 'action' ) !== 'submit' ||
3450 !$request->wasPosted()
3451 ) {
3452 return false;
3453 }
3454
3455 $user = $this->getUser();
3456
3457 if ( !$user->isLoggedIn() ) {
3458 // Anons have predictable edit tokens
3459 return false;
3460 }
3461 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3462 return false;
3463 }
3464
3465 $title = $this->getTitle();
3466 $errors = MediaWikiServices::getInstance()->getPermissionManager()
3467 ->getPermissionErrors( 'edit', $user, $title );
3468 if ( count( $errors ) !== 0 ) {
3469 return false;
3470 }
3471
3472 return true;
3473 }
3474
3480 private function userCanEditOrCreate(
3481 User $user,
3483 ) {
3484 $pm = MediaWikiServices::getInstance()->getPermissionManager();
3485 return $pm->quickUserCan( 'edit', $user, $title )
3486 && ( $this->getTitle()->exists() ||
3487 $pm->quickUserCan( 'create', $user, $title ) );
3488 }
3489
3493 public function getHeadLinksArray() {
3494 $tags = [];
3495 $config = $this->getConfig();
3496
3497 $canonicalUrl = $this->mCanonicalUrl;
3498
3499 $tags['meta-generator'] = Html::element( 'meta', [
3500 'name' => 'generator',
3501 'content' => 'MediaWiki ' . MW_VERSION,
3502 ] );
3503
3504 if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3505 // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3506 // fallbacks should come before the primary value so we need to reverse the array.
3507 foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3508 $tags["meta-referrer-$i"] = Html::element( 'meta', [
3509 'name' => 'referrer',
3510 'content' => $policy,
3511 ] );
3512 }
3513 }
3514
3515 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3516 if ( $p !== 'index,follow' ) {
3517 // http://www.robotstxt.org/wc/meta-user.html
3518 // Only show if it's different from the default robots policy
3519 $tags['meta-robots'] = Html::element( 'meta', [
3520 'name' => 'robots',
3521 'content' => $p,
3522 ] );
3523 }
3524
3525 foreach ( $this->mMetatags as $tag ) {
3526 if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3527 $a = 'http-equiv';
3528 $tag[0] = substr( $tag[0], 5 );
3529 } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3530 $a = 'property';
3531 } else {
3532 $a = 'name';
3533 }
3534 $tagName = "meta-{$tag[0]}";
3535 if ( isset( $tags[$tagName] ) ) {
3536 $tagName .= $tag[1];
3537 }
3538 $tags[$tagName] = Html::element( 'meta',
3539 [
3540 $a => $tag[0],
3541 'content' => $tag[1]
3542 ]
3543 );
3544 }
3545
3546 foreach ( $this->mLinktags as $tag ) {
3547 $tags[] = Html::element( 'link', $tag );
3548 }
3549
3550 # Universal edit button
3551 if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3552 if ( $this->userCanEditOrCreate( $this->getUser(), $this->getTitle() ) ) {
3553 // Original UniversalEditButton
3554 $msg = $this->msg( 'edit' )->text();
3555 $tags['universal-edit-button'] = Html::element( 'link', [
3556 'rel' => 'alternate',
3557 'type' => 'application/x-wiki',
3558 'title' => $msg,
3559 'href' => $this->getTitle()->getEditURL(),
3560 ] );
3561 // Alternate edit link
3562 $tags['alternative-edit'] = Html::element( 'link', [
3563 'rel' => 'edit',
3564 'title' => $msg,
3565 'href' => $this->getTitle()->getEditURL(),
3566 ] );
3567 }
3568 }
3569
3570 # Generally the order of the favicon and apple-touch-icon links
3571 # should not matter, but Konqueror (3.5.9 at least) incorrectly
3572 # uses whichever one appears later in the HTML source. Make sure
3573 # apple-touch-icon is specified first to avoid this.
3574 if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3575 $tags['apple-touch-icon'] = Html::element( 'link', [
3576 'rel' => 'apple-touch-icon',
3577 'href' => $config->get( 'AppleTouchIcon' )
3578 ] );
3579 }
3580
3581 if ( $config->get( 'Favicon' ) !== false ) {
3582 $tags['favicon'] = Html::element( 'link', [
3583 'rel' => 'shortcut icon',
3584 'href' => $config->get( 'Favicon' )
3585 ] );
3586 }
3587
3588 # OpenSearch description link
3589 $tags['opensearch'] = Html::element( 'link', [
3590 'rel' => 'search',
3591 'type' => 'application/opensearchdescription+xml',
3592 'href' => wfScript( 'opensearch_desc' ),
3593 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3594 ] );
3595
3596 # Real Simple Discovery link, provides auto-discovery information
3597 # for the MediaWiki API (and potentially additional custom API
3598 # support such as WordPress or Twitter-compatible APIs for a
3599 # blogging extension, etc)
3600 $tags['rsd'] = Html::element( 'link', [
3601 'rel' => 'EditURI',
3602 'type' => 'application/rsd+xml',
3603 // Output a protocol-relative URL here if $wgServer is protocol-relative.
3604 // Whether RSD accepts relative or protocol-relative URLs is completely
3605 // undocumented, though.
3606 'href' => wfExpandUrl( wfAppendQuery(
3607 wfScript( 'api' ),
3608 [ 'action' => 'rsd' ] ),
3610 ),
3611 ] );
3612
3613 # Language variants
3614 if ( !$config->get( 'DisableLangConversion' ) ) {
3615 $lang = $this->getTitle()->getPageLanguage();
3616 if ( $lang->hasVariants() ) {
3617 $variants = $lang->getVariants();
3618 foreach ( $variants as $variant ) {
3619 $tags["variant-$variant"] = Html::element( 'link', [
3620 'rel' => 'alternate',
3621 'hreflang' => LanguageCode::bcp47( $variant ),
3622 'href' => $this->getTitle()->getLocalURL(
3623 [ 'variant' => $variant ] )
3624 ]
3625 );
3626 }
3627 # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3628 $tags["variant-x-default"] = Html::element( 'link', [
3629 'rel' => 'alternate',
3630 'hreflang' => 'x-default',
3631 'href' => $this->getTitle()->getLocalURL() ] );
3632 }
3633 }
3634
3635 # Copyright
3636 if ( $this->copyrightUrl !== null ) {
3637 $copyright = $this->copyrightUrl;
3638 } else {
3639 $copyright = '';
3640 if ( $config->get( 'RightsPage' ) ) {
3641 $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3642
3643 if ( $copy ) {
3644 $copyright = $copy->getLocalURL();
3645 }
3646 }
3647
3648 if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3649 $copyright = $config->get( 'RightsUrl' );
3650 }
3651 }
3652
3653 if ( $copyright ) {
3654 $tags['copyright'] = Html::element( 'link', [
3655 'rel' => 'license',
3656 'href' => $copyright ]
3657 );
3658 }
3659
3660 # Feeds
3661 if ( $config->get( 'Feed' ) ) {
3662 $feedLinks = [];
3663
3664 foreach ( $this->getSyndicationLinks() as $format => $link ) {
3665 # Use the page name for the title. In principle, this could
3666 # lead to issues with having the same name for different feeds
3667 # corresponding to the same page, but we can't avoid that at
3668 # this low a level.
3669
3670 $feedLinks[] = $this->feedLink(
3671 $format,
3672 $link,
3673 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3674 $this->msg(
3675 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3676 )->text()
3677 );
3678 }
3679
3680 # Recent changes feed should appear on every page (except recentchanges,
3681 # that would be redundant). Put it after the per-page feed to avoid
3682 # changing existing behavior. It's still available, probably via a
3683 # menu in your browser. Some sites might have a different feed they'd
3684 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3685 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3686 # If so, use it instead.
3687 $sitename = $config->get( 'Sitename' );
3688 $overrideSiteFeed = $config->get( 'OverrideSiteFeed' );
3689 if ( $overrideSiteFeed ) {
3690 foreach ( $overrideSiteFeed as $type => $feedUrl ) {
3691 // Note, this->feedLink escapes the url.
3692 $feedLinks[] = $this->feedLink(
3693 $type,
3694 $feedUrl,
3695 $this->msg( "site-{$type}-feed", $sitename )->text()
3696 );
3697 }
3698 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3699 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3700 foreach ( $this->getAdvertisedFeedTypes() as $format ) {
3701 $feedLinks[] = $this->feedLink(
3702 $format,
3703 $rctitle->getLocalURL( [ 'feed' => $format ] ),
3704 # For grep: 'site-rss-feed', 'site-atom-feed'
3705 $this->msg( "site-{$format}-feed", $sitename )->text()
3706 );
3707 }
3708 }
3709
3710 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3711 # manipulating or removing existing feed tags. If you want to add new feeds, you should
3712 # use OutputPage::addFeedLink() instead.
3713 $this->getHookRunner()->onAfterBuildFeedLinks( $feedLinks );
3714
3715 $tags += $feedLinks;
3716 }
3717
3718 # Canonical URL
3719 if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3720 if ( $canonicalUrl !== false ) {
3721 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3722 } elseif ( $this->isArticleRelated() ) {
3723 // This affects all requests where "setArticleRelated" is true. This is
3724 // typically all requests that show content (query title, curid, oldid, diff),
3725 // and all wikipage actions (edit, delete, purge, info, history etc.).
3726 // It does not apply to File pages and Special pages.
3727 // 'history' and 'info' actions address page metadata rather than the page
3728 // content itself, so they may not be canonicalized to the view page url.
3729 // TODO: this ought to be better encapsulated in the Action class.
3730 $action = Action::getActionName( $this->getContext() );
3731 if ( in_array( $action, [ 'history', 'info' ] ) ) {
3732 $query = "action={$action}";
3733 } else {
3734 $query = '';
3735 }
3736 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3737 } else {
3738 $reqUrl = $this->getRequest()->getRequestURL();
3739 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3740 }
3741 }
3742 if ( $canonicalUrl !== false ) {
3743 $tags[] = Html::element( 'link', [
3744 'rel' => 'canonical',
3745 'href' => $canonicalUrl
3746 ] );
3747 }
3748
3749 // Allow extensions to add, remove and/or otherwise manipulate these links
3750 // If you want only to *add* <head> links, please use the addHeadItem()
3751 // (or addHeadItems() for multiple items) method instead.
3752 // This hook is provided as a last resort for extensions to modify these
3753 // links before the output is sent to client.
3754 $this->getHookRunner()->onOutputPageAfterGetHeadLinksArray( $tags, $this );
3755
3756 return $tags;
3757 }
3758
3767 private function feedLink( $type, $url, $text ) {
3768 return Html::element( 'link', [
3769 'rel' => 'alternate',
3770 'type' => "application/$type+xml",
3771 'title' => $text,
3772 'href' => $url ]
3773 );
3774 }
3775
3785 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3786 $options = [];
3787 if ( $media ) {
3788 $options['media'] = $media;
3789 }
3790 if ( $condition ) {
3791 $options['condition'] = $condition;
3792 }
3793 if ( $dir ) {
3794 $options['dir'] = $dir;
3795 }
3796 $this->styles[$style] = $options;
3797 }
3798
3806 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3807 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3808 # If wanted, and the interface is right-to-left, flip the CSS
3809 $style_css = CSSJanus::transform( $style_css, true, false );
3810 }
3811 $this->mInlineStyles .= Html::inlineStyle( $style_css );
3812 }
3813
3819 protected function buildExemptModules() {
3820 $chunks = [];
3821
3822 // Requirements:
3823 // - Within modules provided by the software (core, skin, extensions),
3824 // styles from skin stylesheets should be overridden by styles
3825 // from modules dynamically loaded with JavaScript.
3826 // - Styles from site-specific, private, and user modules should override
3827 // both of the above.
3828 //
3829 // The effective order for stylesheets must thus be:
3830 // 1. Page style modules, formatted server-side by ResourceLoaderClientHtml.
3831 // 2. Dynamically-loaded styles, inserted client-side by mw.loader.
3832 // 3. Styles that are site-specific, private or from the user, formatted
3833 // server-side by this function.
3834 //
3835 // The 'ResourceLoaderDynamicStyles' marker helps JavaScript know where
3836 // point #2 is.
3837
3838 // Add legacy styles added through addStyle()/addInlineStyle() here
3839 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3840
3841 // Things that go after the ResourceLoaderDynamicStyles marker
3842 $append = [];
3843 $separateReq = [ 'site.styles', 'user.styles' ];
3844 foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3845 if ( $moduleNames ) {
3846 $append[] = $this->makeResourceLoaderLink(
3847 array_diff( $moduleNames, $separateReq ),
3848 ResourceLoaderModule::TYPE_STYLES
3849 );
3850
3851 foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3852 // These require their own dedicated request in order to support "@import"
3853 // syntax, which is incompatible with concatenation. (T147667, T37562)
3854 $append[] = $this->makeResourceLoaderLink( $name,
3855 ResourceLoaderModule::TYPE_STYLES
3856 );
3857 }
3858 }
3859 }
3860 if ( $append ) {
3861 $chunks[] = Html::element(
3862 'meta',
3863 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3864 );
3865 $chunks = array_merge( $chunks, $append );
3866 }
3867
3868 return self::combineWrappedStrings( $chunks );
3869 }
3870
3874 public function buildCssLinksArray() {
3875 $links = [];
3876
3877 foreach ( $this->styles as $file => $options ) {
3878 $link = $this->styleLink( $file, $options );
3879 if ( $link ) {
3880 $links[$file] = $link;
3881 }
3882 }
3883 return $links;
3884 }
3885
3893 protected function styleLink( $style, array $options ) {
3894 if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
3895 return '';
3896 }
3897
3898 if ( isset( $options['media'] ) ) {
3899 $media = self::transformCssMedia( $options['media'] );
3900 if ( $media === null ) {
3901 return '';
3902 }
3903 } else {
3904 $media = 'all';
3905 }
3906
3907 if ( substr( $style, 0, 1 ) == '/' ||
3908 substr( $style, 0, 5 ) == 'http:' ||
3909 substr( $style, 0, 6 ) == 'https:' ) {
3910 $url = $style;
3911 } else {
3912 $config = $this->getConfig();
3913 // Append file hash as query parameter
3914 $url = self::transformResourcePath(
3915 $config,
3916 $config->get( 'StylePath' ) . '/' . $style
3917 );
3918 }
3919
3920 $link = Html::linkedStyle( $url, $media );
3921
3922 if ( isset( $options['condition'] ) ) {
3923 $condition = htmlspecialchars( $options['condition'] );
3924 $link = "<!--[if $condition]>$link<![endif]-->";
3925 }
3926 return $link;
3927 }
3928
3950 public static function transformResourcePath( Config $config, $path ) {
3951 global $IP;
3952
3953 $localDir = $IP;
3954 $remotePathPrefix = $config->get( 'ResourceBasePath' );
3955 if ( $remotePathPrefix === '' ) {
3956 // The configured base path is required to be empty string for
3957 // wikis in the domain root
3958 $remotePath = '/';
3959 } else {
3960 $remotePath = $remotePathPrefix;
3961 }
3962 if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
3963 // - Path is outside wgResourceBasePath, ignore.
3964 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
3965 return $path;
3966 }
3967 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
3968 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
3969 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
3970 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
3971 $uploadPath = $config->get( 'UploadPath' );
3972 if ( strpos( $path, $uploadPath ) === 0 ) {
3973 $localDir = $config->get( 'UploadDirectory' );
3974 $remotePathPrefix = $remotePath = $uploadPath;
3975 }
3976
3977 $path = RelPath::getRelativePath( $path, $remotePath );
3978 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
3979 }
3980
3992 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3993 $hash = md5_file( "$localPath/$file" );
3994 if ( $hash === false ) {
3995 wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3996 $hash = '';
3997 }
3998 return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
3999 }
4000
4008 public static function transformCssMedia( $media ) {
4009 global $wgRequest;
4010
4011 // https://www.w3.org/TR/css3-mediaqueries/#syntax
4012 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
4013
4014 // Switch in on-screen display for media testing
4015 $switches = [
4016 'printable' => 'print',
4017 'handheld' => 'handheld',
4018 ];
4019 foreach ( $switches as $switch => $targetMedia ) {
4020 if ( $wgRequest->getBool( $switch ) ) {
4021 if ( $media == $targetMedia ) {
4022 $media = '';
4023 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
4024 /* This regex will not attempt to understand a comma-separated media_query_list
4025 *
4026 * Example supported values for $media:
4027 * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
4028 * Example NOT supported value for $media:
4029 * '3d-glasses, screen, print and resolution > 90dpi'
4030 *
4031 * If it's a print request, we never want any kind of screen stylesheets
4032 * If it's a handheld request (currently the only other choice with a switch),
4033 * we don't want simple 'screen' but we might want screen queries that
4034 * have a max-width or something, so we'll pass all others on and let the
4035 * client do the query.
4036 */
4037 if ( $targetMedia == 'print' || $media == 'screen' ) {
4038 return null;
4039 }
4040 }
4041 }
4042 }
4043
4044 return $media;
4045 }
4046
4055 public function addWikiMsg( ...$args ) {
4056 $name = array_shift( $args );
4057 $this->addWikiMsgArray( $name, $args );
4058 }
4059
4068 public function addWikiMsgArray( $name, $args ) {
4069 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
4070 }
4071
4098 public function wrapWikiMsg( $wrap, ...$msgSpecs ) {
4099 $s = $wrap;
4100 foreach ( $msgSpecs as $n => $spec ) {
4101 if ( is_array( $spec ) ) {
4102 $args = $spec;
4103 $name = array_shift( $args );
4104 } else {
4105 $args = [];
4106 $name = $spec;
4107 }
4108 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4109 }
4110 $this->addWikiTextAsInterface( $s );
4111 }
4112
4118 public function isTOCEnabled() {
4119 return $this->mEnableTOC;
4120 }
4121
4129 public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4130 $themes = ResourceLoaderOOUIModule::getSkinThemeMap();
4131 $theme = $themes[$skinName] ?? $themes['default'];
4132 // For example, 'OOUI\WikimediaUITheme'.
4133 $themeClass = "OOUI\\{$theme}Theme";
4134 OOUI\Theme::setSingleton( new $themeClass() );
4135 OOUI\Element::setDefaultDir( $dir );
4136 }
4137
4144 public function enableOOUI() {
4145 self::setupOOUI(
4146 strtolower( $this->getSkin()->getSkinName() ),
4147 $this->getLanguage()->getDir()
4148 );
4149 $this->addModuleStyles( [
4150 'oojs-ui-core.styles',
4151 'oojs-ui.styles.indicators',
4152 'mediawiki.widgets.styles',
4153 'oojs-ui-core.icons',
4154 ] );
4155 }
4156
4167 public function getCSPNonce() {
4168 return $this->CSP->getNonce();
4169 }
4170
4177 public function getCSP() {
4178 return $this->CSP;
4179 }
4180}
getUser()
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:40
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,...
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
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.
wfClearOutputBuffers()
More legible than passing a 'false' parameter to wfResetOutputBuffers():
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 path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
getContext()
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:643
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
IContextSource $context
getWikiPage()
Get the WikiPage object.
setContext(IContextSource $context)
A mutable version of ResourceLoaderContext.
WebRequest clone which takes values from a provided array.
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:63
exists()
Returns true if file exists in the repository.
Definition File.php:966
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Content for JavaScript pages.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
setArray( $array)
Set the link list to a given 2-d array First key is the namespace, second is the DB key,...
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
This serves as the entry point to the MediaWiki session handling system.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:161
This is one of the Core classes and should be read at least once by any new developers.
isArticle()
Return whether the content displayed page is related to the source of the corresponding article on th...
addLinkHeader( $header)
Add an HTTP Link: header.
setFileVersion( $file)
Set the displayed file version.
$mScripts
Used for JavaScript (predates ResourceLoader)
getDisplayTitle()
Returns page display title.
getCdnCacheEpoch( $reqTime, $maxAge)
disable()
Disable output completely, i.e.
addLink(array $linkarr)
Add a new <link> tag to the page header.
addWikiMsg(... $args)
Add a wikitext-formatted message to the output.
ResourceLoader $mResourceLoader
ContentSecurityPolicy $CSP
addWikiTextTitleInternal( $text, Title $title, $linestart, $interface, $wrapperClass=null)
Add wikitext with a custom Title object.
static array $cacheVaryCookies
A cache of the names of the cookies that will influence the cache.
getCanonicalUrl()
Returns the URL to be used for the <link rel=canonical>> if one is set.
getPageTitle()
Return the "page title", i.e.
isArticleRelated()
Return whether this page is related an article on the wiki.
addCategoryLinksToLBAndGetResult(array $categories)
getLanguageLinks()
Get the list of language links.
array $mModules
userCanEditOrCreate(User $user, LinkTarget $title)
addScriptFile( $file, $unused=null)
Add a JavaScript file to be loaded as <script> on this page.
array $mJsConfigVars
array $mImageTimeKeys
allowClickjacking()
Turn off frame-breaking.
filterModules(array $modules, $position=null, $type=ResourceLoaderModule::TYPE_COMBINED)
Filter an array of modules to remove insufficiently trustworthy members, and modules which are no lon...
wrapWikiTextAsInterface( $wrapperClass, $text)
Convert wikitext in the user interface language to HTML and add it to the buffer with a <div class="$...
getSyndicationLinks()
Return URLs for each supported syndication format for this page.
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
addMeta( $name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
$mFeedLinks
Handles the Atom / RSS links.
bool $mNewSectionLink
setCopyrightUrl( $url)
Set the copyright URL to send with the output.
setRobotPolicy( $policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
__construct(IContextSource $context)
Constructor for OutputPage.
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
getTemplateIds()
Get the templates used on this page.
array $mLinktags
setDisplayTitle( $html)
Same as page title but only contains name of the page, not any other text.
setPageTitle( $name)
"Page title" means the contents of <h1>.
output( $return=false)
Finally, all the text has been munged and accumulated into the object, let's actually output it:
array $mIndicators
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
setLastModified( $timestamp)
Override the last modified timestamp.
considerCacheSettingsFinal()
Set the expectation that cache control will not change after this point.
setLanguageLinks(array $newLinkArray)
Reset the language links and add new language links.
addFeedLink( $format, $href)
Add a feed link to the page header.
setTitle(Title $t)
Set the Title object to use.
setCategoryLinks(array $categories)
Reset the category links (but not the category list) and add $categories.
getAdvertisedFeedTypes()
Return effective list of advertised feed types.
getFileVersion()
Get the displayed file version.
setSyndicated( $show=true)
Add or remove feed links in the page header This is mainly kept for backward compatibility,...
bool $mHideNewSectionLink
bool $mIsArticle
Is the displayed content related to the source of the corresponding wiki article.
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
showsCopyright()
Return whether the standard copyright should be shown for the current page.
sendCacheControl()
Send cache control HTTP headers.
setTarget( $target)
Sets ResourceLoader target for load.php links.
string[][] $mMetatags
Should be private.
addCategoryLinks(array $categories)
Add an array of categories, with names in the keys.
int $mRevisionId
To include the variable {{REVISIONID}}.
showLagWarning( $lag)
Show a warning about replica DB lag.
clearSubtitle()
Clear the subtitles.
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
showFatalError( $message)
Output an error page.
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
getJsConfigVars()
Get the javascript config vars to include on this page.
getHTML()
Get the body HTML.
addContentOverride(LinkTarget $target, Content $content)
Add a mapping from a LinkTarget to a Content, for things like page preview.
getFeaturePolicyReportOnly()
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
setCopyright( $hasCopyright)
Set whether the standard copyright should be shown for the current page.
array $mAllowedModules
What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
getJSVars()
Get an array containing the variables to be set in mw.config in JavaScript.
bool $mPreventClickjacking
Controls if anti-clickjacking / frame-breaking headers will be sent.
haveCacheVaryCookies()
Check if the request has a cache-varying cookie header If it does, it's very important that we don't ...
isTOCEnabled()
Whether the output has a table of contents.
versionRequired( $version)
Display an error page indicating that a given version of MediaWiki is required to use it.
reduceAllowedModules( $type, $level)
Limit the highest level of CSS/JS untrustworthiness allowed.
addInlineStyle( $style_css, $flip='noflip')
Adds inline CSS styles Internal use only.
getRevisionTimestamp()
Get the timestamp of displayed revision.
preventClickjacking( $enable=true)
Set a flag which will cause an X-Frame-Options header appropriate for edit pages to be sent.
bool $mPrintable
We have to set isPrintable().
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
getHeadItemsArray()
Get an array of head items.
isPrintable()
Return whether the page is "printable".
getModules( $filter=false, $position=null, $param='mModules', $type=ResourceLoaderModule::TYPE_COMBINED)
Get the list of modules to include on this page.
setRedirectedFrom( $t)
Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
getFeedAppendQuery()
Will currently always return null.
bool $cacheIsFinal
See OutputPage::couldBePublicCached.
addModuleStyles( $modules)
Load the styles of one or more style-only ResourceLoader modules on this page.
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
loadSkinModules( $sk)
Transfer styles and JavaScript modules from skin.
array $mAdditionalBodyClasses
Additional <body> classes; there are also <body> classes from other sources.
returnToMain( $unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request,...
array $mHeadItems
Array of elements in "<head>".
isSyndicated()
Should we output feed links for this page?
$mLinkHeader
Link: header contents.
styleLink( $style, array $options)
Generate <link> tags for stylesheets.
addHtmlClasses( $classes)
Add a class to the <html> element.
parseAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language and return the HTML.
addParserOutputMetadata(ParserOutput $parserOutput)
Add all metadata associated with a ParserOutput object, but without the actual HTML.
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
string $mRedirect
addElement( $element, array $attribs=[], $contents='')
Shortcut for adding an Html::element via addHTML.
array $mModuleStyles
makeResourceLoaderLink( $modules, $only, array $extraQuery=[])
Explicily load or embed modules on a page.
getSubtitle()
Get the subtitle.
showPermissionsErrorPage(array $errors, $action=null)
Output a standard permission error page.
addSubtitle( $str)
Add $str to the subtitle.
static setupOOUI( $skinName='default', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
addParserOutputContent(ParserOutput $parserOutput, $poOptions=[])
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
getPreventClickjacking()
Get the prevent-clickjacking flag.
enableOOUI()
Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with MediaW...
getIndexPolicy()
Get the current index policy for the page as a string.
getCategories( $type='all')
Get the list of category names this page belongs to.
getIndicators()
Get the indicators associated with this page.
$mProperties
Additional key => value data.
parseInlineAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language, strip paragraph wrapper, and return the HTML.
addWikiMsgArray( $name, $args)
Add a wikitext-formatted message to the output.
array $contentOverrides
Map Title to Content.
addBodyClasses( $classes)
Add a class to the <body> element.
addParserOutputText(ParserOutput $parserOutput, $poOptions=[])
Add the HTML associated with a ParserOutput object, without any metadata.
warnModuleTargetFilter( $moduleName)
string $mBodytext
Contains all of the "<body>" content.
int $mContainsNewMagic
addScript( $script)
Add raw HTML to the list of scripts (including <script> tag, etc.) Internal use only.
clearHTML()
Clear the body HTML.
array $mSubtitle
Contains the page subtitle.
addVaryHeader( $header, array $option=null)
Add an HTTP header that will influence on the cache.
addStyle( $style, $media='', $condition='', $dir='')
Add a local or specified stylesheet, with the given media options.
getLinkHeader()
Return a Link: header.
setPrintable()
Set the page as printable, i.e.
getResourceLoader()
Get a ResourceLoader object associated with this OutputPage.
feedLink( $type, $url, $text)
Generate a "<link rel/>" for a feed.
bool $mNoGallery
Comes from the parser.
getModuleStyles( $filter=false, $position=null)
Get the list of style-only modules to load on this page.
enableClientCache( $state)
Use enableClientCache(false) to force it to send nocache headers.
userCanPreview()
To make it harder for someone to slip a user a fake JavaScript or CSS preview, a random token is asso...
array $limitReportJSData
Profiling data.
parseInternal( $text, $title, $linestart, $interface)
Parse wikitext and return the HTML (internal implementation helper)
getFileSearchOptions()
Get the files used on this page.
array $mAdditionalHtmlClasses
Additional <html> classes; This should be rarely modified; prefer mAdditionalBodyClasses.
isRevisionCurrent()
Whether the revision displayed is the latest revision of the page.
addHTML( $text)
Append $text to the body HTML.
formatPermissionsErrorMessage(array $errors, $action=null)
Format a list of error messages.
string $mRevisionTimestamp
string null $copyrightUrl
The URL to send in a <link> element with rel=license.
getCSP()
Get the ContentSecurityPolicy object.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
getHTMLTitle()
Return the "HTML title", i.e.
disallowUserJs()
Do not allow scripts which can be modified by wiki users to load on this page; only allow scripts bun...
string $mHTMLtitle
Stores contents of "<title>" tag.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
setCanonicalUrl( $url)
Set the URL to be used for the <link rel=canonical>>.
ParserOptions $mParserOptions
lazy initialised, use parserOptions()
addTemplate(&$template)
Add the output of a QuickTemplate to the output buffer.
getRlClient()
Call this to freeze the module queue and JS config and create a formatter.
static transformFilePath( $remotePathPrefix, $localPath, $file)
Utility method for transformResourceFilePath().
callable[] $contentOverrideCallbacks
int $mCdnMaxageLimit
Upper limit on mCdnMaxage.
ResourceLoaderClientHtml $rlClient
setArticleBodyOnly( $only)
Set whether the output should only contain the body of the article, without any skin,...
addInlineScript( $script)
Add a self-contained script tag with the given contents Internal use only.
addAcceptLanguage()
T23672: Add Accept-Language to Vary header if there's no 'variant' parameter in GET.
setStatusCode( $statusCode)
Set the HTTP status code to send with the output.
parserOptions()
Get/set the ParserOptions object to use for wikitext parsing.
getCacheVaryCookies()
Get the list of cookie names that will influence the cache.
getLastSeenUserTalkRevId()
Get the revision ID for the last user talk page revision viewed by the talk page owner.
ResourceLoaderContext $rlClientContext
showErrorPage( $title, $msg, $params=[])
Output a standard error page.
getLinkTags()
Returns the current <link> tags.
getArticleBodyOnly()
Return whether the output will contain only the body of the article.
setProperty( $name, $value)
Set an additional output property.
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
checkLastModified( $timestamp)
checkLastModified tells the client to use the client-cached page if possible.
getVaryHeader()
Return a Vary: header on which to vary caches.
string $mLastModified
Used for sending cache control.
addWikiTextAsContent( $text, $linestart=true, Title $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
showNewSectionLink()
Show an "add new section" link?
bool $mDoNothing
Whether output is disabled.
string $mPageTitle
The contents of.
string $mPageLinkTitle
Used by skin template.
addHeadItem( $name, $value)
Add or replace a head item to the output.
getRevisionId()
Get the displayed revision ID.
setIndicators(array $indicators)
Add an array of indicators, with their identifiers as array keys and HTML contents as values.
bool $mEnableTOC
Whether parser output contains a table of contents.
Title $mRedirectedFrom
If the current page was reached through a redirect, $mRedirectedFrom contains the Title of the redire...
lowerCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header to $maxage if that is lower t...
getRobotPolicy()
Get the current robot policy for the page as a string in the form <index policy>,<follow policy>.
forceHideNewSectionLink()
Forcibly hide the new section link?
bool $mHasCopyright
Is the content subject to copyright.
string null $mTarget
ResourceLoader target for load.php links.
int $mCdnMaxage
Cache stuff.
addBacklinkSubtitle(Title $title, $query=[])
Add a subtitle containing a backlink to a page.
array $mTemplateIds
getAllowedModules( $type)
Show what level of JavaScript / CSS untrustworthiness is allowed on this page.
string $displayTitle
The displayed title of the page.
prependHTML( $text)
Prepend $text to the body HTML.
addLanguageLinks(array $newLinkArray)
Add new language links.
array $mLanguageLinks
Array of Interwiki Prefixed (non DB key) Titles (e.g.
setArticleRelated( $newVal)
Set whether this page is related an article on the wiki Setting false will cause the change of "artic...
string $mInlineStyles
Inline CSS styles.
getFollowPolicy()
Get the current follow policy for the page as a string.
array $mFileVersion
addModules( $modules)
Load one or more ResourceLoader modules on this page.
buildExemptModules()
Build exempt modules and legacy non-ResourceLoader styles.
string $mRedirectCode
getProperty( $name)
Get an additional output property.
addWikiTextAsInterface( $text, $linestart=true, Title $title=null)
Convert wikitext in the user interface language to HTML and add it to the buffer.
headElement(Skin $sk, $includeStyle=true)
prepareErrorPage( $pageTitle, $htmlTitle=false)
Prepare this object to display an error page; disable caching and indexing, clear the current text an...
array $mCategoryLinks
addContentOverrideCallback(callable $callback)
Add a callback for mapping from a Title to a Content object, for things like page preview.
static buildBacklinkSubtitle(Title $title, $query=[])
Build message object for a subtitle containing a backlink to a page.
setFeedAppendQuery( $val)
Add default feeds to the page header This is mainly kept for backward compatibility,...
getBottomScripts()
JS stuff to put at the bottom of the <body>.
addReturnTo( $title, array $query=[], $text=null, $options=[])
Add a "return to" link pointing to a specified title.
getMetaTags()
Returns the current <meta> tags.
getOriginTrials()
Get the Origin-Trial header values.
string bool $mCanonicalUrl
bool $mArticleBodyOnly
Flag if output should only contain the body of the article.
setSubtitle( $str)
Replace the subtitle with $str.
couldBePublicCached()
Whether the output might become publicly cached.
array $rlExemptStyleModules
getCategoryLinks()
Get the list of category links, in a 2-D array with the following format: $arr[$type][] = $link,...
getFrameOptions()
Get the X-Frame-Options header value (without the name part), or false if there isn't one.
addHeadItems( $values)
Add one or more head items to the output.
static combineWrappedStrings(array $chunks)
Combine WrappedString chunks and filter out empty ones.
bool $mIsArticleRelated
Stores "article flag" toggle.
array $mCategories
getUnprefixedDisplayTitle()
Returns page display title without namespace prefix if possible.
getCSPNonce()
Get (and set if not yet set) the CSP nonce.
array $mVaryHeader
Headers that cause the cache to vary.
hasHeadItem( $name)
Check if the header item $name is already set.
array $styles
An array of stylesheet filenames (relative from skins path), with options for CSS media,...
isDisabled()
Return whether the output will be completely disabled.
Set options of the Parser.
preventClickjacking( $flag=null)
Get or set the prevent-clickjacking flag.
getExtraCSPStyleSrcs()
Get extra Content-Security-Policy 'style-src' directives.
getExtraCSPScriptSrcs()
Get extra Content-Security-Policy 'script-src' directives.
getText( $options=[])
Get the output HTML.
getExtraCSPDefaultSrcs()
Get extra Content-Security-Policy 'default-src' directives.
Load and configure a ResourceLoader client on an HTML page.
setModules(array $modules)
Ensure one or more modules are loaded.
static makeLoad(ResourceLoaderContext $mainContext, array $modules, $only, array $extraQuery=[], $nonce=null)
Explicitly load or embed modules on a page.
setConfig(array $vars)
Set mw.config variables.
setExemptStates(array $states)
Set state of special modules that are handled by the caller manually.
setModuleStyles(array $modules)
Ensure the styles of one or more modules are loaded.
Context object that contains information about the state of a specific ResourceLoader web request.
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
getOrigin()
Get this module's origin.
ResourceLoader is a loading system for JavaScript and CSS resources.
static makeInlineScript( $script, $nonce=null)
Make an HTML script that runs given JS code after startup and base modules.
static inDebugMode()
Determine whether debug mode is on.
static makeConfigSetScript(array $configuration)
Return JS code which will set the MediaWiki configuration array to the given value.
static makeLoaderQuery(array $modules, $lang, $skin, $user=null, $version=null, $debug=false, $only=null, $printable=false, $handheld=false, array $extraQuery=[])
Build a query array (array representation of query string) for load.php.
The main skin class which provides methods and properties for all other skins.
Definition Skin.php:41
getHtmlElementAttributes()
Return values for <html> element.
Definition Skin.php:489
getSkinName()
Definition Skin.php:168
getPageClasses( $title)
TODO: document.
Definition Skin.php:451
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Represents a title within MediaWiki.
Definition Title.php:42
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:64
const PROTO_CANONICAL
Definition Defines.php:213
const PROTO_CURRENT
Definition Defines.php:212
const NS_SPECIAL
Definition Defines.php:59
const PROTO_RELATIVE
Definition Defines.php:211
const NS_CATEGORY
Definition Defines.php:84
Interface for configuration instances.
Definition Config.php:29
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Base interface for content objects.
Definition Content.php:35
Interface for objects which can provide a MediaWiki context on request.
getConfig()
Get the site configuration.
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
Result wrapper for grabbing data queried from an IDatabase object.
if( $line===false) $args
Definition mcc.php:124
const DB_REPLICA
Definition defines.php:25
$IP
Definition rebuild.php:19
$content
Definition router.php:76
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!isset( $args[0])) $lang
$header