MediaWiki 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 images, give the user-agent
1911 // a hint about a remote hosts from which images may be served. Launched in T123582.
1912 if ( $this->getConfig()->get( 'ImagePreconnect' ) && count( $parserOutput->getImages() ) ) {
1913 $preconnect = [];
1914 // Optimization: Instead of processing each image, assume that wikis either serve both
1915 // foreign and local from the same remote hostname (e.g. public wikis at WMF), or that
1916 // foreign images are common enough to be worth the preconnect (e.g. private wikis).
1917 $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
1918 $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$preconnect ) {
1919 $preconnect[] = $repo->getZoneUrl( 'thumb' );
1920 } );
1921 // Consider both foreign and local repos. While LocalRepo by default uses a relative
1922 // path on the same domain, wiki farms may configure it to use a dedicated hostname.
1923 $preconnect[] = $repoGroup->getLocalRepo()->getZoneUrl( 'thumb' );
1924 foreach ( $preconnect as $url ) {
1925 $host = parse_url( $url, PHP_URL_HOST );
1926 // It is expected that file URLs are often path-only, without hostname (T317329).
1927 if ( $host ) {
1928 $this->addLink( [ 'rel' => 'preconnect', 'href' => '//' . $host ] );
1929 break;
1930 }
1931 }
1932 }
1933
1934 // Template versioning...
1935 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1936 if ( isset( $this->mTemplateIds[$ns] ) ) {
1937 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1938 } else {
1939 $this->mTemplateIds[$ns] = $dbks;
1940 }
1941 }
1942 // File versioning...
1943 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1944 $this->mImageTimeKeys[$dbk] = $data;
1945 }
1946
1947 // Hooks registered in the object
1948 $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1949 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1950 list( $hookName, $data ) = $hookInfo;
1951 if ( isset( $parserOutputHooks[$hookName] ) ) {
1952 $parserOutputHooks[$hookName]( $this, $parserOutput, $data );
1953 }
1954 }
1955
1956 // Enable OOUI if requested via ParserOutput
1957 if ( $parserOutput->getEnableOOUI() ) {
1958 $this->enableOOUI();
1959 }
1960
1961 // Include parser limit report
1962 if ( !$this->limitReportJSData ) {
1963 $this->limitReportJSData = $parserOutput->getLimitReportJSData();
1964 }
1965
1966 // Link flags are ignored for now, but may in the future be
1967 // used to mark individual language links.
1968 $linkFlags = [];
1969 $this->getHookRunner()->onLanguageLinks( $this->getTitle(), $this->mLanguageLinks, $linkFlags );
1970 $this->getHookRunner()->onOutputPageParserOutput( $this, $parserOutput );
1971
1972 // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
1973 // so that extensions may modify ParserOutput to toggle TOC.
1974 // This cannot be moved to addParserOutputText because that is not
1975 // called by EditPage for Preview.
1976 if ( $parserOutput->getTOCHTML() ) {
1977 $this->mEnableTOC = true;
1978 }
1979 }
1980
1989 public function addParserOutputContent( ParserOutput $parserOutput, $poOptions = [] ) {
1990 $this->addParserOutputText( $parserOutput, $poOptions );
1991
1992 $this->addModules( $parserOutput->getModules() );
1993 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1994
1995 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1996 }
1997
2005 public function addParserOutputText( ParserOutput $parserOutput, $poOptions = [] ) {
2006 $text = $parserOutput->getText( $poOptions );
2007 $this->getHookRunner()->onOutputPageBeforeHTML( $this, $text );
2008 $this->addHTML( $text );
2009 }
2010
2017 public function addParserOutput( ParserOutput $parserOutput, $poOptions = [] ) {
2018 $this->addParserOutputMetadata( $parserOutput );
2019 $this->addParserOutputText( $parserOutput, $poOptions );
2020 }
2021
2027 public function addTemplate( &$template ) {
2028 $this->addHTML( $template->getHTML() );
2029 }
2030
2042 public function parseAsContent( $text, $linestart = true ) {
2043 return $this->parseInternal(
2044 $text, $this->getTitle(), $linestart, /*interface*/false
2045 )->getText( [
2046 'enableSectionEditLinks' => false,
2047 'wrapperDivClass' => ''
2048 ] );
2049 }
2050
2063 public function parseAsInterface( $text, $linestart = true ) {
2064 return $this->parseInternal(
2065 $text, $this->getTitle(), $linestart, /*interface*/true
2066 )->getText( [
2067 'enableSectionEditLinks' => false,
2068 'wrapperDivClass' => ''
2069 ] );
2070 }
2071
2086 public function parseInlineAsInterface( $text, $linestart = true ) {
2087 return Parser::stripOuterParagraph(
2088 $this->parseAsInterface( $text, $linestart )
2089 );
2090 }
2091
2104 private function parseInternal( $text, $title, $linestart, $interface ) {
2105 if ( $title === null ) {
2106 throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
2107 }
2108
2109 $popts = $this->parserOptions();
2110
2111 $oldInterface = $popts->setInterfaceMessage( (bool)$interface );
2112
2113 $parserOutput = MediaWikiServices::getInstance()->getParser()->getFreshParser()->parse(
2114 $text, $title, $popts,
2115 $linestart, true, $this->mRevisionId
2116 );
2117
2118 $popts->setInterfaceMessage( $oldInterface );
2119
2120 return $parserOutput;
2121 }
2122
2128 public function setCdnMaxage( $maxage ) {
2129 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2130 }
2131
2141 public function lowerCdnMaxage( $maxage ) {
2142 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2143 $this->setCdnMaxage( $this->mCdnMaxage );
2144 }
2145
2158 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2159 $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2160 $maxTTL = $maxTTL ?: $this->getConfig()->get( 'CdnMaxAge' );
2161
2162 if ( $mtime === null || $mtime === false ) {
2163 return; // entity does not exist
2164 }
2165
2166 $age = MWTimestamp::time() - (int)wfTimestamp( TS_UNIX, $mtime );
2167 $adaptiveTTL = max( 0.9 * $age, $minTTL );
2168 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2169
2170 $this->lowerCdnMaxage( (int)$adaptiveTTL );
2171 }
2172
2180 public function enableClientCache( $state ) {
2181 return wfSetVar( $this->mEnableClientCache, $state );
2182 }
2183
2190 public function couldBePublicCached() {
2191 if ( !$this->cacheIsFinal ) {
2192 // - The entry point handles its own caching and/or doesn't use OutputPage.
2193 // (such as load.php, AjaxDispatcher, or MediaWiki\Rest\EntryPoint).
2194 //
2195 // - Or, we haven't finished processing the main part of the request yet
2196 // (e.g. Action::show, SpecialPage::execute), and the state may still
2197 // change via enableClientCache().
2198 return true;
2199 }
2200 // e.g. various error-type pages disable all client caching
2201 return $this->mEnableClientCache;
2202 }
2203
2213 public function considerCacheSettingsFinal() {
2214 $this->cacheIsFinal = true;
2215 }
2216
2222 public function getCacheVaryCookies() {
2223 if ( self::$cacheVaryCookies === null ) {
2224 $config = $this->getConfig();
2225 self::$cacheVaryCookies = array_values( array_unique( array_merge(
2226 SessionManager::singleton()->getVaryCookies(),
2227 [
2228 'forceHTTPS',
2229 ],
2230 $config->get( 'CacheVaryCookies' )
2231 ) ) );
2232 $this->getHookRunner()->onGetCacheVaryCookies( $this, self::$cacheVaryCookies );
2233 }
2234 return self::$cacheVaryCookies;
2235 }
2236
2243 public function haveCacheVaryCookies() {
2244 $request = $this->getRequest();
2245 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2246 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2247 wfDebug( __METHOD__ . ": found $cookieName" );
2248 return true;
2249 }
2250 }
2251 wfDebug( __METHOD__ . ": no cache-varying cookies found" );
2252 return false;
2253 }
2254
2264 public function addVaryHeader( $header, array $option = null ) {
2265 if ( $option !== null && count( $option ) > 0 ) {
2267 'The $option parameter to addVaryHeader is ignored since MediaWiki 1.34',
2268 '1.34' );
2269 }
2270 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2271 $this->mVaryHeader[$header] = null;
2272 }
2273 }
2274
2281 public function getVaryHeader() {
2282 // If we vary on cookies, let's make sure it's always included here too.
2283 if ( $this->getCacheVaryCookies() ) {
2284 $this->addVaryHeader( 'Cookie' );
2285 }
2286
2287 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2288 $this->addVaryHeader( $header, $options );
2289 }
2290 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2291 }
2292
2298 public function addLinkHeader( $header ) {
2299 $this->mLinkHeader[] = $header;
2300 }
2301
2307 public function getLinkHeader() {
2308 if ( !$this->mLinkHeader ) {
2309 return false;
2310 }
2311
2312 return 'Link: ' . implode( ',', $this->mLinkHeader );
2313 }
2314
2322 private function addAcceptLanguage() {
2323 $title = $this->getTitle();
2324 if ( !$title instanceof Title ) {
2325 return;
2326 }
2327
2328 $lang = $title->getPageLanguage();
2329 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2330 $this->addVaryHeader( 'Accept-Language' );
2331 }
2332 }
2333
2344 public function preventClickjacking( $enable = true ) {
2345 $this->mPreventClickjacking = $enable;
2346 }
2347
2353 public function allowClickjacking() {
2354 $this->mPreventClickjacking = false;
2355 }
2356
2363 public function getPreventClickjacking() {
2364 return $this->mPreventClickjacking;
2365 }
2366
2374 public function getFrameOptions() {
2375 $config = $this->getConfig();
2376 if ( $config->get( 'BreakFrames' ) ) {
2377 return 'DENY';
2378 } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2379 return $config->get( 'EditPageFrameOptions' );
2380 }
2381 return false;
2382 }
2383
2390 private function getOriginTrials() {
2391 $config = $this->getConfig();
2392
2393 return $config->get( 'OriginTrials' );
2394 }
2395
2396 private function getReportTo() {
2397 $config = $this->getConfig();
2398
2399 $expiry = $config->get( 'ReportToExpiry' );
2400
2401 if ( !$expiry ) {
2402 return false;
2403 }
2404
2405 $endpoints = $config->get( 'ReportToEndpoints' );
2406
2407 if ( !$endpoints ) {
2408 return false;
2409 }
2410
2411 $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
2412
2413 foreach ( $endpoints as $endpoint ) {
2414 $output['endpoints'][] = [ 'url' => $endpoint ];
2415 }
2416
2417 return json_encode( $output, JSON_UNESCAPED_SLASHES );
2418 }
2419
2420 private function getFeaturePolicyReportOnly() {
2421 $config = $this->getConfig();
2422
2423 $features = $config->get( 'FeaturePolicyReportOnly' );
2424 return implode( ';', $features );
2425 }
2426
2430 public function sendCacheControl() {
2431 $response = $this->getRequest()->response();
2432 $config = $this->getConfig();
2433
2434 $this->addVaryHeader( 'Cookie' );
2435 $this->addAcceptLanguage();
2436
2437 # don't serve compressed data to clients who can't handle it
2438 # maintain different caches for logged-in users and non-logged in ones
2439 $response->header( $this->getVaryHeader() );
2440
2441 if ( $this->mEnableClientCache ) {
2442 if (
2443 $config->get( 'UseCdn' ) &&
2444 !$response->hasCookies() &&
2445 // The client might use methods other than cookies to appear logged-in.
2446 // E.g. HTTP headers, or query parameter tokens, OAuth, etc.
2447 !SessionManager::getGlobalSession()->isPersistent() &&
2448 !$this->isPrintable() &&
2449 $this->mCdnMaxage != 0 &&
2450 !$this->haveCacheVaryCookies()
2451 ) {
2452 # We'll purge the proxy cache for anons explicitly, but require end user agents
2453 # to revalidate against the proxy on each visit.
2454 # IMPORTANT! The CDN needs to replace the Cache-Control header with
2455 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2456 wfDebug( __METHOD__ .
2457 ": local proxy caching; {$this->mLastModified} **", 'private' );
2458 # start with a shorter timeout for initial testing
2459 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2460 $response->header( "Cache-Control: " .
2461 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2462 } else {
2463 # We do want clients to cache if they can, but they *must* check for updates
2464 # on revisiting the page, after the max-age period.
2465 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2466
2467 if ( $response->hasCookies() || SessionManager::getGlobalSession()->isPersistent() ) {
2468 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2469 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2470 } else {
2471 $response->header(
2472 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $config->get( 'LoggedOutMaxAge' ) ) . ' GMT'
2473 );
2474 $response->header(
2475 "Cache-Control: private, must-revalidate, max-age={$config->get( 'LoggedOutMaxAge' )}"
2476 );
2477 }
2478 }
2479 if ( $this->mLastModified ) {
2480 $response->header( "Last-Modified: {$this->mLastModified}" );
2481 }
2482 } else {
2483 wfDebug( __METHOD__ . ": no caching **", 'private' );
2484
2485 # In general, the absence of a last modified header should be enough to prevent
2486 # the client from using its cache. We send a few other things just to make sure.
2487 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2488 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2489 $response->header( 'Pragma: no-cache' );
2490 }
2491 }
2492
2498 public function loadSkinModules( $sk ) {
2499 foreach ( $sk->getDefaultModules() as $group => $modules ) {
2500 if ( $group === 'styles' ) {
2501 foreach ( $modules as $key => $moduleMembers ) {
2502 $this->addModuleStyles( $moduleMembers );
2503 }
2504 } else {
2505 $this->addModules( $modules );
2506 }
2507 }
2508 }
2509
2520 public function output( $return = false ) {
2521 if ( $this->mDoNothing ) {
2522 return $return ? '' : null;
2523 }
2524
2525 $response = $this->getRequest()->response();
2526 $config = $this->getConfig();
2527
2528 if ( $this->mRedirect != '' ) {
2529 # Standards require redirect URLs to be absolute
2530 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2531
2532 $redirect = $this->mRedirect;
2533 $code = $this->mRedirectCode;
2534 $content = '';
2535
2536 if ( $this->getHookRunner()->onBeforePageRedirect( $this, $redirect, $code ) ) {
2537 if ( $code == '301' || $code == '303' ) {
2538 if ( !$config->get( 'DebugRedirects' ) ) {
2539 $response->statusHeader( $code );
2540 }
2541 $this->mLastModified = wfTimestamp( TS_RFC2822 );
2542 }
2543 if ( $config->get( 'VaryOnXFP' ) ) {
2544 $this->addVaryHeader( 'X-Forwarded-Proto' );
2545 }
2546 $this->sendCacheControl();
2547
2548 $response->header( "Content-Type: text/html; charset=utf-8" );
2549 if ( $config->get( 'DebugRedirects' ) ) {
2550 $url = htmlspecialchars( $redirect );
2551 $content = "<!DOCTYPE html>\n<html>\n<head>\n"
2552 . "<title>Redirect</title>\n</head>\n<body>\n"
2553 . "<p>Location: <a href=\"$url\">$url</a></p>\n"
2554 . "</body>\n</html>\n";
2555
2556 if ( !$return ) {
2558 }
2559
2560 } else {
2561 $response->header( 'Location: ' . $redirect );
2562 }
2563 }
2564
2565 return $return ? $content : null;
2566 } elseif ( $this->mStatusCode ) {
2567 $response->statusHeader( $this->mStatusCode );
2568 }
2569
2570 # Buffer output; final headers may depend on later processing
2571 ob_start();
2572
2573 $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2574 $response->header( 'Content-language: ' .
2575 MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
2576
2577 $linkHeader = $this->getLinkHeader();
2578 if ( $linkHeader ) {
2579 $response->header( $linkHeader );
2580 }
2581
2582 // Prevent framing, if requested
2583 $frameOptions = $this->getFrameOptions();
2584 if ( $frameOptions ) {
2585 $response->header( "X-Frame-Options: $frameOptions" );
2586 }
2587
2588 $originTrials = $this->getOriginTrials();
2589 foreach ( $originTrials as $originTrial ) {
2590 $response->header( "Origin-Trial: $originTrial", false );
2591 }
2592
2593 $reportTo = $this->getReportTo();
2594 if ( $reportTo ) {
2595 $response->header( "Report-To: $reportTo" );
2596 }
2597
2598 $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
2599 if ( $featurePolicyReportOnly ) {
2600 $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
2601 }
2602
2603 if ( $this->mArticleBodyOnly ) {
2604 $this->CSP->sendHeaders();
2605 echo $this->mBodytext;
2606 } else {
2607 // Enable safe mode if requested (T152169)
2608 if ( $this->getRequest()->getBool( 'safemode' ) ) {
2609 $this->disallowUserJs();
2610 }
2611
2612 $sk = $this->getSkin();
2613 $this->loadSkinModules( $sk );
2614
2615 MWDebug::addModules( $this );
2616
2617 // Hook that allows last minute changes to the output page, e.g.
2618 // adding of CSS or Javascript by extensions, adding CSP sources.
2619 $this->getHookRunner()->onBeforePageDisplay( $this, $sk );
2620
2621 $this->CSP->sendHeaders();
2622
2623 try {
2624 $sk->outputPage();
2625 } catch ( Exception $e ) {
2626 ob_end_clean(); // bug T129657
2627 throw $e;
2628 }
2629 }
2630
2631 try {
2632 // This hook allows last minute changes to final overall output by modifying output buffer
2633 $this->getHookRunner()->onAfterFinalPageOutput( $this );
2634 } catch ( Exception $e ) {
2635 ob_end_clean(); // bug T129657
2636 throw $e;
2637 }
2638
2639 $this->sendCacheControl();
2640
2641 if ( $return ) {
2642 return ob_get_clean();
2643 } else {
2644 ob_end_flush();
2645 return null;
2646 }
2647 }
2648
2659 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2660 $this->setPageTitle( $pageTitle );
2661 if ( $htmlTitle !== false ) {
2662 $this->setHTMLTitle( $htmlTitle );
2663 }
2664 $this->setRobotPolicy( 'noindex,nofollow' );
2665 $this->setArticleRelated( false );
2666 $this->enableClientCache( false );
2667 $this->mRedirect = '';
2668 $this->clearSubtitle();
2669 $this->clearHTML();
2670 }
2671
2684 public function showErrorPage( $title, $msg, $params = [] ) {
2685 if ( !$title instanceof Message ) {
2686 $title = $this->msg( $title );
2687 }
2688
2689 $this->prepareErrorPage( $title );
2690
2691 if ( $msg instanceof Message ) {
2692 if ( $params !== [] ) {
2693 trigger_error( 'Argument ignored: $params. The message parameters argument '
2694 . 'is discarded when the $msg argument is a Message object instead of '
2695 . 'a string.', E_USER_NOTICE );
2696 }
2697 $this->addHTML( $msg->parseAsBlock() );
2698 } else {
2699 $this->addWikiMsgArray( $msg, $params );
2700 }
2701
2702 $this->returnToMain();
2703 }
2704
2711 public function showPermissionsErrorPage( array $errors, $action = null ) {
2712 $services = MediaWikiServices::getInstance();
2713 $permissionManager = $services->getPermissionManager();
2714 foreach ( $errors as $key => $error ) {
2715 $errors[$key] = (array)$error;
2716 }
2717
2718 // For some action (read, edit, create and upload), display a "login to do this action"
2719 // error if all of the following conditions are met:
2720 // 1. the user is not logged in
2721 // 2. the only error is insufficient permissions (i.e. no block or something else)
2722 // 3. the error can be avoided simply by logging in
2723
2724 if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2725 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2726 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2727 && ( $permissionManager->groupHasPermission( 'user', $action )
2728 || $permissionManager->groupHasPermission( 'autoconfirmed', $action ) )
2729 ) {
2730 $displayReturnto = null;
2731
2732 # Due to T34276, if a user does not have read permissions,
2733 # $this->getTitle() will just give Special:Badtitle, which is
2734 # not especially useful as a returnto parameter. Use the title
2735 # from the request instead, if there was one.
2736 $request = $this->getRequest();
2737 $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2738 if ( $action == 'edit' ) {
2739 $msg = 'whitelistedittext';
2740 $displayReturnto = $returnto;
2741 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2742 $msg = 'nocreatetext';
2743 } elseif ( $action == 'upload' ) {
2744 $msg = 'uploadnologintext';
2745 } else { # Read
2746 $msg = 'loginreqpagetext';
2747 $displayReturnto = Title::newMainPage();
2748 }
2749
2750 $query = [];
2751
2752 if ( $returnto ) {
2753 $query['returnto'] = $returnto->getPrefixedText();
2754
2755 if ( !$request->wasPosted() ) {
2756 $returntoquery = $request->getValues();
2757 unset( $returntoquery['title'] );
2758 unset( $returntoquery['returnto'] );
2759 unset( $returntoquery['returntoquery'] );
2760 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2761 }
2762 }
2763
2764 $title = SpecialPage::getTitleFor( 'Userlogin' );
2765 $linkRenderer = $services->getLinkRenderer();
2766 $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
2767 $loginLink = $linkRenderer->makeKnownLink(
2768 $title,
2769 $this->msg( 'loginreqlink' )->text(),
2770 [],
2771 $query
2772 );
2773
2774 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2775 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
2776
2777 # Don't return to a page the user can't read otherwise
2778 # we'll end up in a pointless loop
2779 if ( $displayReturnto && $permissionManager->userCan(
2780 'read', $this->getUser(), $displayReturnto
2781 ) ) {
2782 $this->returnToMain( null, $displayReturnto );
2783 }
2784 } else {
2785 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2786 $this->addWikiTextAsInterface( $this->formatPermissionsErrorMessage( $errors, $action ) );
2787 }
2788 }
2789
2796 public function versionRequired( $version ) {
2797 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2798
2799 $this->addWikiMsg( 'versionrequiredtext', $version );
2800 $this->returnToMain();
2801 }
2802
2810 public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2811 if ( $action == null ) {
2812 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2813 } else {
2814 $action_desc = $this->msg( "action-$action" )->plain();
2815 $text = $this->msg(
2816 'permissionserrorstext-withaction',
2817 count( $errors ),
2818 $action_desc
2819 )->plain() . "\n\n";
2820 }
2821
2822 if ( count( $errors ) > 1 ) {
2823 $text .= '<ul class="permissions-errors">' . "\n";
2824
2825 foreach ( $errors as $error ) {
2826 $text .= '<li>';
2827 $text .= $this->msg( ...$error )->plain();
2828 $text .= "</li>\n";
2829 }
2830 $text .= '</ul>';
2831 } else {
2832 $text .= "<div class=\"permissions-errors\">\n" .
2833 $this->msg( ...reset( $errors ) )->plain() .
2834 "\n</div>";
2835 }
2836
2837 return $text;
2838 }
2839
2849 public function showLagWarning( $lag ) {
2850 $config = $this->getConfig();
2851 if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2852 $lag = floor( $lag ); // floor to avoid nano seconds to display
2853 $message = $lag < $config->get( 'SlaveLagCritical' )
2854 ? 'lag-warn-normal'
2855 : 'lag-warn-high';
2856 // For grep: mw-lag-warn-normal, mw-lag-warn-high
2857 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2858 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2859 }
2860 }
2861
2868 public function showFatalError( $message ) {
2869 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2870
2871 $this->addHTML( $message );
2872 }
2873
2882 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2883 $linkRenderer = MediaWikiServices::getInstance()
2884 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
2885 $link = $this->msg( 'returnto' )->rawParams(
2886 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
2887 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2888 }
2889
2898 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2899 if ( $returnto == null ) {
2900 $returnto = $this->getRequest()->getText( 'returnto' );
2901 }
2902
2903 if ( $returntoquery == null ) {
2904 $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2905 }
2906
2907 if ( $returnto === '' ) {
2908 $returnto = Title::newMainPage();
2909 }
2910
2911 if ( is_object( $returnto ) ) {
2912 $titleObj = $returnto;
2913 } else {
2914 $titleObj = Title::newFromText( $returnto );
2915 }
2916 // We don't want people to return to external interwiki. That
2917 // might potentially be used as part of a phishing scheme
2918 if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
2919 $titleObj = Title::newMainPage();
2920 }
2921
2922 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2923 }
2924
2925 private function getRlClientContext() {
2926 if ( !$this->rlClientContext ) {
2928 [], // modules; not relevant
2929 $this->getLanguage()->getCode(),
2930 $this->getSkin()->getSkinName(),
2931 $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
2932 null, // version; not relevant
2934 null, // only; not relevant
2935 $this->isPrintable(),
2936 $this->getRequest()->getBool( 'handheld' )
2937 );
2938 $this->rlClientContext = new ResourceLoaderContext(
2939 $this->getResourceLoader(),
2940 new FauxRequest( $query )
2941 );
2942 if ( $this->contentOverrideCallbacks ) {
2943 $this->rlClientContext = new DerivativeResourceLoaderContext( $this->rlClientContext );
2944 $this->rlClientContext->setContentOverrideCallback( function ( Title $title ) {
2945 foreach ( $this->contentOverrideCallbacks as $callback ) {
2946 $content = $callback( $title );
2947 if ( $content !== null ) {
2948 $text = ContentHandler::getContentText( $content );
2949 if ( strpos( $text, '</script>' ) !== false ) {
2950 // Proactively replace this so that we can display a message
2951 // to the user, instead of letting it go to Html::inlineScript(),
2952 // where it would be considered a server-side issue.
2953 $titleFormatted = $title->getPrefixedText();
2955 Xml::encodeJsCall( 'mw.log.error', [
2956 "Cannot preview $titleFormatted due to script-closing tag."
2957 ] )
2958 );
2959 }
2960 return $content;
2961 }
2962 }
2963 return null;
2964 } );
2965 }
2966 }
2967 return $this->rlClientContext;
2968 }
2969
2981 public function getRlClient() {
2982 if ( !$this->rlClient ) {
2983 $context = $this->getRlClientContext();
2984 $rl = $this->getResourceLoader();
2985 $this->addModules( [
2986 'user',
2987 'user.options',
2988 ] );
2989 $this->addModuleStyles( [
2990 'site.styles',
2991 'noscript',
2992 'user.styles',
2993 ] );
2994 $this->getSkin()->setupSkinUserCss( $this );
2995
2996 // Prepare exempt modules for buildExemptModules()
2997 $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
2998 $exemptStates = [];
2999 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
3000
3001 // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
3002 // Separate user-specific batch for improved cache-hit ratio.
3003 $userBatch = [ 'user.styles', 'user' ];
3004 $siteBatch = array_diff( $moduleStyles, $userBatch );
3005 $dbr = wfGetDB( DB_REPLICA );
3006 ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $siteBatch );
3007 ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $userBatch );
3008
3009 // Filter out modules handled by buildExemptModules()
3010 $moduleStyles = array_filter( $moduleStyles,
3011 function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3012 $module = $rl->getModule( $name );
3013 if ( $module ) {
3014 $group = $module->getGroup();
3015 if ( isset( $exemptGroups[$group] ) ) {
3016 $exemptStates[$name] = 'ready';
3017 if ( !$module->isKnownEmpty( $context ) ) {
3018 // E.g. Don't output empty <styles>
3019 $exemptGroups[$group][] = $name;
3020 }
3021 return false;
3022 }
3023 }
3024 return true;
3025 }
3026 );
3027 $this->rlExemptStyleModules = $exemptGroups;
3028
3030 'target' => $this->getTarget(),
3031 'nonce' => $this->CSP->getNonce(),
3032 // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3033 // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3034 // modules enqueud for loading on this page is filtered to just those.
3035 // However, to make sure we also apply the restriction to dynamic dependencies and
3036 // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3037 // StartupModule so that the client-side registry will not contain any restricted
3038 // modules either. (T152169, T185303)
3039 'safemode' => ( $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
3040 <= ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
3041 ) ? '1' : null,
3042 ] );
3043 $rlClient->setConfig( $this->getJSVars() );
3044 $rlClient->setModules( $this->getModules( /*filter*/ true ) );
3045 $rlClient->setModuleStyles( $moduleStyles );
3046 $rlClient->setExemptStates( $exemptStates );
3047 $this->rlClient = $rlClient;
3048 }
3049 return $this->rlClient;
3050 }
3051
3057 public function headElement( Skin $sk, $includeStyle = true ) {
3058 $config = $this->getConfig();
3059 $userdir = $this->getLanguage()->getDir();
3060 $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
3061
3062 $pieces = [];
3063 $htmlAttribs = Sanitizer::mergeAttributes( Sanitizer::mergeAttributes(
3064 $this->getRlClient()->getDocumentAttributes(),
3066 ), [ 'class' => implode( ' ', $this->mAdditionalHtmlClasses ) ] );
3067 $pieces[] = Html::htmlHeader( $htmlAttribs );
3068 $pieces[] = Html::openElement( 'head' );
3069
3070 if ( $this->getHTMLTitle() == '' ) {
3071 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3072 }
3073
3074 if ( !Html::isXmlMimeType( $config->get( 'MimeType' ) ) ) {
3075 // Add <meta charset="UTF-8">
3076 // This should be before <title> since it defines the charset used by
3077 // text including the text inside <title>.
3078 // The spec recommends defining XHTML5's charset using the XML declaration
3079 // instead of meta.
3080 // Our XML declaration is output by Html::htmlHeader.
3081 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3082 // https://html.spec.whatwg.org/multipage/semantics.html#charset
3083 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3084 }
3085
3086 $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
3087 $pieces[] = $this->getRlClient()->getHeadHtml( $htmlAttribs['class'] ?? null );
3088 $pieces[] = $this->buildExemptModules();
3089 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3090 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3091
3092 // This library is intended to run on older browsers that MediaWiki no longer
3093 // supports as Grade A. For these Grade C browsers, we provide an experience
3094 // using only HTML and CSS. But, where standards-compliant browsers are able to
3095 // style unknown HTML elements without issue, old IE ignores these styles.
3096 // The html5shiv library fixes that.
3097 // Use an IE conditional comment to serve the script only to old IE
3098 $shivUrl = $config->get( 'ResourceBasePath' ) . '/resources/lib/html5shiv/html5shiv.js';
3099 $pieces[] = '<!--[if lt IE 9]>' .
3100 Html::linkedScript( $shivUrl, $this->CSP->getNonce() ) .
3101 '<![endif]-->';
3102
3103 $pieces[] = Html::closeElement( 'head' );
3104
3105 $bodyClasses = $this->mAdditionalBodyClasses;
3106 $bodyClasses[] = 'mediawiki';
3107
3108 # Classes for LTR/RTL directionality support
3109 $bodyClasses[] = $userdir;
3110 $bodyClasses[] = "sitedir-$sitedir";
3111
3112 $underline = $this->getUser()->getOption( 'underline' );
3113 if ( $underline < 2 ) {
3114 // The following classes can be used here:
3115 // * mw-underline-always
3116 // * mw-underline-never
3117 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3118 }
3119
3120 if ( $this->getLanguage()->capitalizeAllNouns() ) {
3121 # A <body> class is probably not the best way to do this . . .
3122 $bodyClasses[] = 'capitalize-all-nouns';
3123 }
3124
3125 // Parser feature migration class
3126 // The idea is that this will eventually be removed, after the wikitext
3127 // which requires it is cleaned up.
3128 $bodyClasses[] = 'mw-hide-empty-elt';
3129
3130 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3131 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3132 $bodyClasses[] =
3133 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3134
3135 $bodyAttrs = [];
3136 // While the implode() is not strictly needed, it's used for backwards compatibility
3137 // (this used to be built as a string and hooks likely still expect that).
3138 $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3139
3140 // Allow skins and extensions to add body attributes they need
3141 // Get ones from deprecated method
3142 if ( method_exists( $sk, 'addToBodyAttributes' ) ) {
3144 $sk->addToBodyAttributes( $this, $bodyAttrs );
3145 wfDeprecated( 'Skin::addToBodyAttributes method to add body attributes', '1.35' );
3146 }
3147
3148 // Then run the hook, the recommended way of adding body attributes now
3149 $this->getHookRunner()->onOutputPageBodyAttributes( $this, $sk, $bodyAttrs );
3150
3151 $pieces[] = Html::openElement( 'body', $bodyAttrs );
3152
3153 return self::combineWrappedStrings( $pieces );
3154 }
3155
3161 public function getResourceLoader() {
3162 if ( $this->mResourceLoader === null ) {
3163 // Lazy-initialise as needed
3164 $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3165 }
3166 return $this->mResourceLoader;
3167 }
3168
3177 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3178 // Apply 'target' and 'origin' filters
3179 $modules = $this->filterModules( (array)$modules, null, $only );
3180
3182 $this->getRlClientContext(),
3183 $modules,
3184 $only,
3185 $extraQuery,
3186 $this->CSP->getNonce()
3187 );
3188 }
3189
3196 protected static function combineWrappedStrings( array $chunks ) {
3197 // Filter out empty values
3198 $chunks = array_filter( $chunks, 'strlen' );
3199 return WrappedString::join( "\n", $chunks );
3200 }
3201
3208 public function getBottomScripts() {
3209 $chunks = [];
3210 $chunks[] = $this->getRlClient()->getBodyHtml();
3211
3212 // Legacy non-ResourceLoader scripts
3213 $chunks[] = $this->mScripts;
3214
3215 if ( $this->limitReportJSData ) {
3218 [ 'wgPageParseReport' => $this->limitReportJSData ]
3219 ),
3220 $this->CSP->getNonce()
3221 );
3222 }
3223
3224 return self::combineWrappedStrings( $chunks );
3225 }
3226
3233 public function getJsConfigVars() {
3234 return $this->mJsConfigVars;
3235 }
3236
3243 public function addJsConfigVars( $keys, $value = null ) {
3244 if ( is_array( $keys ) ) {
3245 foreach ( $keys as $key => $value ) {
3246 $this->mJsConfigVars[$key] = $value;
3247 }
3248 return;
3249 }
3250
3251 $this->mJsConfigVars[$keys] = $value;
3252 }
3253
3263 public function getJSVars() {
3264 $curRevisionId = 0;
3265 $articleId = 0;
3266 $canonicalSpecialPageName = false; # T23115
3267 $services = MediaWikiServices::getInstance();
3268
3269 $title = $this->getTitle();
3270 $ns = $title->getNamespace();
3271 $nsInfo = $services->getNamespaceInfo();
3272 $canonicalNamespace = $nsInfo->exists( $ns )
3273 ? $nsInfo->getCanonicalName( $ns )
3274 : $title->getNsText();
3275
3276 $sk = $this->getSkin();
3277 // Get the relevant title so that AJAX features can use the correct page name
3278 // when making API requests from certain special pages (T36972).
3279 $relevantTitle = $sk->getRelevantTitle();
3280 $relevantUser = $sk->getRelevantUser();
3281
3282 if ( $ns == NS_SPECIAL ) {
3283 list( $canonicalSpecialPageName, /*...*/ ) =
3284 $services->getSpecialPageFactory()->
3285 resolveAlias( $title->getDBkey() );
3286 } elseif ( $this->canUseWikiPage() ) {
3287 $wikiPage = $this->getWikiPage();
3288 $curRevisionId = $wikiPage->getLatest();
3289 $articleId = $wikiPage->getId();
3290 }
3291
3292 $lang = $title->getPageViewLanguage();
3293
3294 // Pre-process information
3295 $separatorTransTable = $lang->separatorTransformTable();
3296 $separatorTransTable = $separatorTransTable ?: [];
3297 $compactSeparatorTransTable = [
3298 implode( "\t", array_keys( $separatorTransTable ) ),
3299 implode( "\t", $separatorTransTable ),
3300 ];
3301 $digitTransTable = $lang->digitTransformTable();
3302 $digitTransTable = $digitTransTable ?: [];
3303 $compactDigitTransTable = [
3304 implode( "\t", array_keys( $digitTransTable ) ),
3305 implode( "\t", $digitTransTable ),
3306 ];
3307
3308 $user = $this->getUser();
3309
3310 // Internal variables for MediaWiki core
3311 $vars = [
3312 // @internal For mediawiki.page.startup
3313 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3314
3315 // @internal For jquery.tablesorter
3316 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3317 'wgDigitTransformTable' => $compactDigitTransTable,
3318 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3319 'wgMonthNames' => $lang->getMonthNamesArray(),
3320
3321 // @internal For debugging purposes
3322 'wgRequestId' => WebRequest::getRequestId(),
3323
3324 // @internal For mw.loader
3325 'wgCSPNonce' => $this->CSP->getNonce(),
3326 ];
3327
3328 // Start of supported and stable config vars (for use by extensions/gadgets).
3329 $vars += [
3330 'wgCanonicalNamespace' => $canonicalNamespace,
3331 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3332 'wgNamespaceNumber' => $title->getNamespace(),
3333 'wgPageName' => $title->getPrefixedDBkey(),
3334 'wgTitle' => $title->getText(),
3335 'wgCurRevisionId' => $curRevisionId,
3336 'wgRevisionId' => (int)$this->getRevisionId(),
3337 'wgArticleId' => $articleId,
3338 'wgIsArticle' => $this->isArticle(),
3339 'wgIsRedirect' => $title->isRedirect(),
3340 'wgAction' => Action::getActionName( $this->getContext() ),
3341 'wgUserName' => $user->isAnon() ? null : $user->getName(),
3342 'wgUserGroups' => $user->getEffectiveGroups(),
3343 'wgCategories' => $this->getCategories(),
3344 'wgPageContentLanguage' => $lang->getCode(),
3345 'wgPageContentModel' => $title->getContentModel(),
3346 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3347 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3348 ];
3349 if ( $user->isLoggedIn() ) {
3350 $vars['wgUserId'] = $user->getId();
3351 $vars['wgUserEditCount'] = $user->getEditCount();
3352 $userReg = $user->getRegistration();
3353 $vars['wgUserRegistration'] = $userReg ? (int)wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3354 // Get the revision ID of the oldest new message on the user's talk
3355 // page. This can be used for constructing new message alerts on
3356 // the client side.
3357 $userNewMsgRevId = $this->getLastSeenUserTalkRevId();
3358 // Only occupy precious space in the <head> when it is non-null (T53640)
3359 // mw.config.get returns null by default.
3360 if ( $userNewMsgRevId ) {
3361 $vars['wgUserNewMsgRevisionId'] = $userNewMsgRevId;
3362 }
3363 }
3364 $contLang = $services->getContentLanguage();
3365 if ( $contLang->hasVariants() ) {
3366 $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3367 }
3368 // Same test as SkinTemplate
3369 $vars['wgIsProbablyEditable'] = $this->userCanEditOrCreate( $user, $title );
3370 $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle &&
3371 $this->userCanEditOrCreate( $user, $relevantTitle );
3372 foreach ( $title->getRestrictionTypes() as $type ) {
3373 // Following keys are set in $vars:
3374 // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3375 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3376 }
3377 if ( $title->isMainPage() ) {
3378 $vars['wgIsMainPage'] = true;
3379 }
3380 if ( $relevantUser && ( !$relevantUser->isHidden() ||
3381 $services->getPermissionManager()->userHasRight( $user, 'hideuser' ) )
3382 ) {
3383 // T120883 if the user is hidden and the viewer cannot see
3384 // hidden users, pretend like it does not exist at all.
3385 $vars['wgRelevantUserName'] = $relevantUser->getName();
3386 }
3387 // End of stable config vars
3388
3389 if ( $this->mRedirectedFrom ) {
3390 // @internal For skin JS
3391 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3392 }
3393
3394 // Allow extensions to add their custom variables to the mw.config map.
3395 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3396 // page-dependant but site-wide (without state).
3397 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3398 $this->getHookRunner()->onMakeGlobalVariablesScript( $vars, $this );
3399
3400 // Merge in variables from addJsConfigVars last
3401 return array_merge( $vars, $this->getJsConfigVars() );
3402 }
3403
3409 private function getLastSeenUserTalkRevId() {
3410 $services = MediaWikiServices::getInstance();
3411 $user = $this->getUser();
3412 $userHasNewMessages = $services
3413 ->getTalkPageNotificationManager()
3414 ->userHasNewMessages( $user );
3415 if ( !$userHasNewMessages ) {
3416 return null;
3417 }
3418
3419 $timestamp = $services
3420 ->getTalkPageNotificationManager()
3421 ->getLatestSeenMessageTimestamp( $user );
3422
3423 if ( !$timestamp ) {
3424 return null;
3425 }
3426
3427 $revRecord = $services->getRevisionLookup()->getRevisionByTimestamp(
3428 $user->getTalkPage(),
3429 $timestamp
3430 );
3431
3432 if ( !$revRecord ) {
3433 return null;
3434 }
3435
3436 return $revRecord->getId();
3437 }
3438
3448 public function userCanPreview() {
3449 $request = $this->getRequest();
3450 if (
3451 $request->getVal( 'action' ) !== 'submit' ||
3452 !$request->wasPosted()
3453 ) {
3454 return false;
3455 }
3456
3457 $user = $this->getUser();
3458
3459 if ( !$user->isLoggedIn() ) {
3460 // Anons have predictable edit tokens
3461 return false;
3462 }
3463 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3464 return false;
3465 }
3466
3467 $title = $this->getTitle();
3468 $errors = MediaWikiServices::getInstance()->getPermissionManager()
3469 ->getPermissionErrors( 'edit', $user, $title );
3470 if ( count( $errors ) !== 0 ) {
3471 return false;
3472 }
3473
3474 return true;
3475 }
3476
3482 private function userCanEditOrCreate(
3483 User $user,
3485 ) {
3486 $pm = MediaWikiServices::getInstance()->getPermissionManager();
3487 return $pm->quickUserCan( 'edit', $user, $title )
3488 && ( $this->getTitle()->exists() ||
3489 $pm->quickUserCan( 'create', $user, $title ) );
3490 }
3491
3495 public function getHeadLinksArray() {
3496 $tags = [];
3497 $config = $this->getConfig();
3498
3499 $canonicalUrl = $this->mCanonicalUrl;
3500
3501 $tags['meta-generator'] = Html::element( 'meta', [
3502 'name' => 'generator',
3503 'content' => 'MediaWiki ' . MW_VERSION,
3504 ] );
3505
3506 if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3507 // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3508 // fallbacks should come before the primary value so we need to reverse the array.
3509 foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3510 $tags["meta-referrer-$i"] = Html::element( 'meta', [
3511 'name' => 'referrer',
3512 'content' => $policy,
3513 ] );
3514 }
3515 }
3516
3517 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3518 if ( $p !== 'index,follow' ) {
3519 // http://www.robotstxt.org/wc/meta-user.html
3520 // Only show if it's different from the default robots policy
3521 $tags['meta-robots'] = Html::element( 'meta', [
3522 'name' => 'robots',
3523 'content' => $p,
3524 ] );
3525 }
3526
3527 foreach ( $this->mMetatags as $tag ) {
3528 if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3529 $a = 'http-equiv';
3530 $tag[0] = substr( $tag[0], 5 );
3531 } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3532 $a = 'property';
3533 } else {
3534 $a = 'name';
3535 }
3536 $tagName = "meta-{$tag[0]}";
3537 if ( isset( $tags[$tagName] ) ) {
3538 $tagName .= $tag[1];
3539 }
3540 $tags[$tagName] = Html::element( 'meta',
3541 [
3542 $a => $tag[0],
3543 'content' => $tag[1]
3544 ]
3545 );
3546 }
3547
3548 foreach ( $this->mLinktags as $tag ) {
3549 $tags[] = Html::element( 'link', $tag );
3550 }
3551
3552 # Universal edit button
3553 if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3554 if ( $this->userCanEditOrCreate( $this->getUser(), $this->getTitle() ) ) {
3555 // Original UniversalEditButton
3556 $msg = $this->msg( 'edit' )->text();
3557 $tags['universal-edit-button'] = Html::element( 'link', [
3558 'rel' => 'alternate',
3559 'type' => 'application/x-wiki',
3560 'title' => $msg,
3561 'href' => $this->getTitle()->getEditURL(),
3562 ] );
3563 // Alternate edit link
3564 $tags['alternative-edit'] = Html::element( 'link', [
3565 'rel' => 'edit',
3566 'title' => $msg,
3567 'href' => $this->getTitle()->getEditURL(),
3568 ] );
3569 }
3570 }
3571
3572 # Generally the order of the favicon and apple-touch-icon links
3573 # should not matter, but Konqueror (3.5.9 at least) incorrectly
3574 # uses whichever one appears later in the HTML source. Make sure
3575 # apple-touch-icon is specified first to avoid this.
3576 if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3577 $tags['apple-touch-icon'] = Html::element( 'link', [
3578 'rel' => 'apple-touch-icon',
3579 'href' => $config->get( 'AppleTouchIcon' )
3580 ] );
3581 }
3582
3583 if ( $config->get( 'Favicon' ) !== false ) {
3584 $tags['favicon'] = Html::element( 'link', [
3585 'rel' => 'shortcut icon',
3586 'href' => $config->get( 'Favicon' )
3587 ] );
3588 }
3589
3590 # OpenSearch description link
3591 $tags['opensearch'] = Html::element( 'link', [
3592 'rel' => 'search',
3593 'type' => 'application/opensearchdescription+xml',
3594 'href' => wfScript( 'opensearch_desc' ),
3595 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3596 ] );
3597
3598 # Real Simple Discovery link, provides auto-discovery information
3599 # for the MediaWiki API (and potentially additional custom API
3600 # support such as WordPress or Twitter-compatible APIs for a
3601 # blogging extension, etc)
3602 $tags['rsd'] = Html::element( 'link', [
3603 'rel' => 'EditURI',
3604 'type' => 'application/rsd+xml',
3605 // Output a protocol-relative URL here if $wgServer is protocol-relative.
3606 // Whether RSD accepts relative or protocol-relative URLs is completely
3607 // undocumented, though.
3608 'href' => wfExpandUrl( wfAppendQuery(
3609 wfScript( 'api' ),
3610 [ 'action' => 'rsd' ] ),
3612 ),
3613 ] );
3614
3615 # Language variants
3616 if ( !$config->get( 'DisableLangConversion' ) ) {
3617 $lang = $this->getTitle()->getPageLanguage();
3618 if ( $lang->hasVariants() ) {
3619 $variants = $lang->getVariants();
3620 foreach ( $variants as $variant ) {
3621 $tags["variant-$variant"] = Html::element( 'link', [
3622 'rel' => 'alternate',
3623 'hreflang' => LanguageCode::bcp47( $variant ),
3624 'href' => $this->getTitle()->getLocalURL(
3625 [ 'variant' => $variant ] )
3626 ]
3627 );
3628 }
3629 # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3630 $tags["variant-x-default"] = Html::element( 'link', [
3631 'rel' => 'alternate',
3632 'hreflang' => 'x-default',
3633 'href' => $this->getTitle()->getLocalURL() ] );
3634 }
3635 }
3636
3637 # Copyright
3638 if ( $this->copyrightUrl !== null ) {
3639 $copyright = $this->copyrightUrl;
3640 } else {
3641 $copyright = '';
3642 if ( $config->get( 'RightsPage' ) ) {
3643 $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3644
3645 if ( $copy ) {
3646 $copyright = $copy->getLocalURL();
3647 }
3648 }
3649
3650 if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3651 $copyright = $config->get( 'RightsUrl' );
3652 }
3653 }
3654
3655 if ( $copyright ) {
3656 $tags['copyright'] = Html::element( 'link', [
3657 'rel' => 'license',
3658 'href' => $copyright ]
3659 );
3660 }
3661
3662 # Feeds
3663 if ( $config->get( 'Feed' ) ) {
3664 $feedLinks = [];
3665
3666 foreach ( $this->getSyndicationLinks() as $format => $link ) {
3667 # Use the page name for the title. In principle, this could
3668 # lead to issues with having the same name for different feeds
3669 # corresponding to the same page, but we can't avoid that at
3670 # this low a level.
3671
3672 $feedLinks[] = $this->feedLink(
3673 $format,
3674 $link,
3675 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3676 $this->msg(
3677 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3678 )->text()
3679 );
3680 }
3681
3682 # Recent changes feed should appear on every page (except recentchanges,
3683 # that would be redundant). Put it after the per-page feed to avoid
3684 # changing existing behavior. It's still available, probably via a
3685 # menu in your browser. Some sites might have a different feed they'd
3686 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3687 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3688 # If so, use it instead.
3689 $sitename = $config->get( 'Sitename' );
3690 $overrideSiteFeed = $config->get( 'OverrideSiteFeed' );
3691 if ( $overrideSiteFeed ) {
3692 foreach ( $overrideSiteFeed as $type => $feedUrl ) {
3693 // Note, this->feedLink escapes the url.
3694 $feedLinks[] = $this->feedLink(
3695 $type,
3696 $feedUrl,
3697 $this->msg( "site-{$type}-feed", $sitename )->text()
3698 );
3699 }
3700 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3701 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3702 foreach ( $this->getAdvertisedFeedTypes() as $format ) {
3703 $feedLinks[] = $this->feedLink(
3704 $format,
3705 $rctitle->getLocalURL( [ 'feed' => $format ] ),
3706 # For grep: 'site-rss-feed', 'site-atom-feed'
3707 $this->msg( "site-{$format}-feed", $sitename )->text()
3708 );
3709 }
3710 }
3711
3712 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3713 # manipulating or removing existing feed tags. If you want to add new feeds, you should
3714 # use OutputPage::addFeedLink() instead.
3715 $this->getHookRunner()->onAfterBuildFeedLinks( $feedLinks );
3716
3717 $tags += $feedLinks;
3718 }
3719
3720 # Canonical URL
3721 if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3722 if ( $canonicalUrl !== false ) {
3723 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3724 } elseif ( $this->isArticleRelated() ) {
3725 // This affects all requests where "setArticleRelated" is true. This is
3726 // typically all requests that show content (query title, curid, oldid, diff),
3727 // and all wikipage actions (edit, delete, purge, info, history etc.).
3728 // It does not apply to File pages and Special pages.
3729 // 'history' and 'info' actions address page metadata rather than the page
3730 // content itself, so they may not be canonicalized to the view page url.
3731 // TODO: this ought to be better encapsulated in the Action class.
3732 $action = Action::getActionName( $this->getContext() );
3733 if ( in_array( $action, [ 'history', 'info' ] ) ) {
3734 $query = "action={$action}";
3735 } else {
3736 $query = '';
3737 }
3738 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3739 } else {
3740 $reqUrl = $this->getRequest()->getRequestURL();
3741 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3742 }
3743 }
3744 if ( $canonicalUrl !== false ) {
3745 $tags[] = Html::element( 'link', [
3746 'rel' => 'canonical',
3747 'href' => $canonicalUrl
3748 ] );
3749 }
3750
3751 // Allow extensions to add, remove and/or otherwise manipulate these links
3752 // If you want only to *add* <head> links, please use the addHeadItem()
3753 // (or addHeadItems() for multiple items) method instead.
3754 // This hook is provided as a last resort for extensions to modify these
3755 // links before the output is sent to client.
3756 $this->getHookRunner()->onOutputPageAfterGetHeadLinksArray( $tags, $this );
3757
3758 return $tags;
3759 }
3760
3769 private function feedLink( $type, $url, $text ) {
3770 return Html::element( 'link', [
3771 'rel' => 'alternate',
3772 'type' => "application/$type+xml",
3773 'title' => $text,
3774 'href' => $url ]
3775 );
3776 }
3777
3787 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3788 $options = [];
3789 if ( $media ) {
3790 $options['media'] = $media;
3791 }
3792 if ( $condition ) {
3793 $options['condition'] = $condition;
3794 }
3795 if ( $dir ) {
3796 $options['dir'] = $dir;
3797 }
3798 $this->styles[$style] = $options;
3799 }
3800
3808 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3809 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3810 # If wanted, and the interface is right-to-left, flip the CSS
3811 $style_css = CSSJanus::transform( $style_css, true, false );
3812 }
3813 $this->mInlineStyles .= Html::inlineStyle( $style_css );
3814 }
3815
3821 protected function buildExemptModules() {
3822 $chunks = [];
3823
3824 // Requirements:
3825 // - Within modules provided by the software (core, skin, extensions),
3826 // styles from skin stylesheets should be overridden by styles
3827 // from modules dynamically loaded with JavaScript.
3828 // - Styles from site-specific, private, and user modules should override
3829 // both of the above.
3830 //
3831 // The effective order for stylesheets must thus be:
3832 // 1. Page style modules, formatted server-side by ResourceLoaderClientHtml.
3833 // 2. Dynamically-loaded styles, inserted client-side by mw.loader.
3834 // 3. Styles that are site-specific, private or from the user, formatted
3835 // server-side by this function.
3836 //
3837 // The 'ResourceLoaderDynamicStyles' marker helps JavaScript know where
3838 // point #2 is.
3839
3840 // Add legacy styles added through addStyle()/addInlineStyle() here
3841 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3842
3843 // Things that go after the ResourceLoaderDynamicStyles marker
3844 $append = [];
3845 $separateReq = [ 'site.styles', 'user.styles' ];
3846 foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3847 if ( $moduleNames ) {
3848 $append[] = $this->makeResourceLoaderLink(
3849 array_diff( $moduleNames, $separateReq ),
3850 ResourceLoaderModule::TYPE_STYLES
3851 );
3852
3853 foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3854 // These require their own dedicated request in order to support "@import"
3855 // syntax, which is incompatible with concatenation. (T147667, T37562)
3856 $append[] = $this->makeResourceLoaderLink( $name,
3857 ResourceLoaderModule::TYPE_STYLES
3858 );
3859 }
3860 }
3861 }
3862 if ( $append ) {
3863 $chunks[] = Html::element(
3864 'meta',
3865 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3866 );
3867 $chunks = array_merge( $chunks, $append );
3868 }
3869
3870 return self::combineWrappedStrings( $chunks );
3871 }
3872
3876 public function buildCssLinksArray() {
3877 $links = [];
3878
3879 foreach ( $this->styles as $file => $options ) {
3880 $link = $this->styleLink( $file, $options );
3881 if ( $link ) {
3882 $links[$file] = $link;
3883 }
3884 }
3885 return $links;
3886 }
3887
3895 protected function styleLink( $style, array $options ) {
3896 if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
3897 return '';
3898 }
3899
3900 if ( isset( $options['media'] ) ) {
3901 $media = self::transformCssMedia( $options['media'] );
3902 if ( $media === null ) {
3903 return '';
3904 }
3905 } else {
3906 $media = 'all';
3907 }
3908
3909 if ( substr( $style, 0, 1 ) == '/' ||
3910 substr( $style, 0, 5 ) == 'http:' ||
3911 substr( $style, 0, 6 ) == 'https:' ) {
3912 $url = $style;
3913 } else {
3914 $config = $this->getConfig();
3915 // Append file hash as query parameter
3916 $url = self::transformResourcePath(
3917 $config,
3918 $config->get( 'StylePath' ) . '/' . $style
3919 );
3920 }
3921
3922 $link = Html::linkedStyle( $url, $media );
3923
3924 if ( isset( $options['condition'] ) ) {
3925 $condition = htmlspecialchars( $options['condition'] );
3926 $link = "<!--[if $condition]>$link<![endif]-->";
3927 }
3928 return $link;
3929 }
3930
3952 public static function transformResourcePath( Config $config, $path ) {
3953 global $IP;
3954
3955 $localDir = $IP;
3956 $remotePathPrefix = $config->get( 'ResourceBasePath' );
3957 if ( $remotePathPrefix === '' ) {
3958 // The configured base path is required to be empty string for
3959 // wikis in the domain root
3960 $remotePath = '/';
3961 } else {
3962 $remotePath = $remotePathPrefix;
3963 }
3964 if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
3965 // - Path is outside wgResourceBasePath, ignore.
3966 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
3967 return $path;
3968 }
3969 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
3970 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
3971 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
3972 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
3973 $uploadPath = $config->get( 'UploadPath' );
3974 if ( strpos( $path, $uploadPath ) === 0 ) {
3975 $localDir = $config->get( 'UploadDirectory' );
3976 $remotePathPrefix = $remotePath = $uploadPath;
3977 }
3978
3979 $path = RelPath::getRelativePath( $path, $remotePath );
3980 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
3981 }
3982
3994 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3995 $hash = md5_file( "$localPath/$file" );
3996 if ( $hash === false ) {
3997 wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3998 $hash = '';
3999 }
4000 return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
4001 }
4002
4010 public static function transformCssMedia( $media ) {
4011 global $wgRequest;
4012
4013 // https://www.w3.org/TR/css3-mediaqueries/#syntax
4014 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
4015
4016 // Switch in on-screen display for media testing
4017 $switches = [
4018 'printable' => 'print',
4019 'handheld' => 'handheld',
4020 ];
4021 foreach ( $switches as $switch => $targetMedia ) {
4022 if ( $wgRequest->getBool( $switch ) ) {
4023 if ( $media == $targetMedia ) {
4024 $media = '';
4025 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
4026 /* This regex will not attempt to understand a comma-separated media_query_list
4027 *
4028 * Example supported values for $media:
4029 * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
4030 * Example NOT supported value for $media:
4031 * '3d-glasses, screen, print and resolution > 90dpi'
4032 *
4033 * If it's a print request, we never want any kind of screen stylesheets
4034 * If it's a handheld request (currently the only other choice with a switch),
4035 * we don't want simple 'screen' but we might want screen queries that
4036 * have a max-width or something, so we'll pass all others on and let the
4037 * client do the query.
4038 */
4039 if ( $targetMedia == 'print' || $media == 'screen' ) {
4040 return null;
4041 }
4042 }
4043 }
4044 }
4045
4046 return $media;
4047 }
4048
4057 public function addWikiMsg( ...$args ) {
4058 $name = array_shift( $args );
4059 $this->addWikiMsgArray( $name, $args );
4060 }
4061
4070 public function addWikiMsgArray( $name, $args ) {
4071 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
4072 }
4073
4100 public function wrapWikiMsg( $wrap, ...$msgSpecs ) {
4101 $s = $wrap;
4102 foreach ( $msgSpecs as $n => $spec ) {
4103 if ( is_array( $spec ) ) {
4104 $args = $spec;
4105 $name = array_shift( $args );
4106 } else {
4107 $args = [];
4108 $name = $spec;
4109 }
4110 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4111 }
4112 $this->addWikiTextAsInterface( $s );
4113 }
4114
4120 public function isTOCEnabled() {
4121 return $this->mEnableTOC;
4122 }
4123
4131 public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4132 $themes = ResourceLoaderOOUIModule::getSkinThemeMap();
4133 $theme = $themes[$skinName] ?? $themes['default'];
4134 // For example, 'OOUI\WikimediaUITheme'.
4135 $themeClass = "OOUI\\{$theme}Theme";
4136 OOUI\Theme::setSingleton( new $themeClass() );
4137 OOUI\Element::setDefaultDir( $dir );
4138 }
4139
4146 public function enableOOUI() {
4147 self::setupOOUI(
4148 strtolower( $this->getSkin()->getSkinName() ),
4149 $this->getLanguage()->getDir()
4150 );
4151 $this->addModuleStyles( [
4152 'oojs-ui-core.styles',
4153 'oojs-ui.styles.indicators',
4154 'mediawiki.widgets.styles',
4155 'oojs-ui-core.icons',
4156 ] );
4157 }
4158
4169 public function getCSPNonce() {
4170 return $this->CSP->getNonce();
4171 }
4172
4179 public function getCSP() {
4180 return $this->CSP;
4181 }
4182}
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,...
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
$IP
Definition WebStart.php:49
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:30
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
$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