MediaWiki REL1_33
OutputPage.php
Go to the documentation of this file.
1<?php
27use Wikimedia\RelPath;
28use Wikimedia\WrappedString;
29use Wikimedia\WrappedStringList;
30
48 protected $mMetatags = [];
49
51 protected $mLinktags = [];
52
54 protected $mCanonicalUrl = false;
55
58 private $mPageTitle = '';
59
68
73 public $mBodytext = '';
74
76 private $mHTMLtitle = '';
77
82 private $mIsArticle = false;
83
85 private $mIsArticleRelated = true;
86
88 private $mHasCopyright = false;
89
94 private $mPrintable = false;
95
100 private $mSubtitle = [];
101
103 public $mRedirect = '';
104
106 protected $mStatusCode;
107
112 protected $mLastModified = '';
113
115 protected $mCategoryLinks = [];
116
118 protected $mCategories = [
119 'hidden' => [],
120 'normal' => [],
121 ];
122
124 protected $mIndicators = [];
125
127 private $mLanguageLinks = [];
128
135 private $mScripts = '';
136
138 protected $mInlineStyles = '';
139
144 public $mPageLinkTitle = '';
145
147 protected $mHeadItems = [];
148
151
153 protected $mModules = [];
154
156 protected $mModuleStyles = [];
157
160
162 private $rlClient;
163
166
169
171 protected $mJsConfigVars = [];
172
174 protected $mTemplateIds = [];
175
177 protected $mImageTimeKeys = [];
178
180 public $mRedirectCode = '';
181
182 protected $mFeedLinksAppendQuery = null;
183
189 protected $mAllowedModules = [
191 ];
192
194 protected $mDoNothing = false;
195
196 // Parser related.
197
199 protected $mContainsNewMagic = 0;
200
205 protected $mParserOptions = null;
206
212 private $mFeedLinks = [];
213
214 // Gwicke work on squid caching? Roughly from 2003.
215 protected $mEnableClientCache = true;
216
218 private $mArticleBodyOnly = false;
219
221 protected $mNewSectionLink = false;
222
224 protected $mHideNewSectionLink = false;
225
231 public $mNoGallery = false;
232
234 protected $mCdnMaxage = 0;
236 protected $mCdnMaxageLimit = INF;
237
243 protected $mPreventClickjacking = true;
244
246 private $mRevisionId = null;
247
249 private $mRevisionTimestamp = null;
250
252 protected $mFileVersion = null;
253
262 protected $styles = [];
263
264 private $mIndexPolicy = 'index';
265 private $mFollowPolicy = 'follow';
266
271 private $mVaryHeader = [
272 'Accept-Encoding' => [ 'match=gzip' ],
273 ];
274
281 private $mRedirectedFrom = null;
282
286 private $mProperties = [];
287
291 private $mTarget = null;
292
296 private $mEnableTOC = false;
297
302
304 private $limitReportJSData = [];
305
307 private $contentOverrides = [];
308
311
315 private $mLinkHeader = [];
316
320 private $CSPNonce;
321
325 private static $cacheVaryCookies = null;
326
334 $this->setContext( $context );
335 }
336
343 public function redirect( $url, $responsecode = '302' ) {
344 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
345 $this->mRedirect = str_replace( "\n", '', $url );
346 $this->mRedirectCode = $responsecode;
347 }
348
354 public function getRedirect() {
355 return $this->mRedirect;
356 }
357
366 public function setCopyrightUrl( $url ) {
367 $this->copyrightUrl = $url;
368 }
369
375 public function setStatusCode( $statusCode ) {
376 $this->mStatusCode = $statusCode;
377 }
378
386 function addMeta( $name, $val ) {
387 array_push( $this->mMetatags, [ $name, $val ] );
388 }
389
396 public function getMetaTags() {
397 return $this->mMetatags;
398 }
399
407 function addLink( array $linkarr ) {
408 array_push( $this->mLinktags, $linkarr );
409 }
410
417 public function getLinkTags() {
418 return $this->mLinktags;
419 }
420
426 function setCanonicalUrl( $url ) {
427 $this->mCanonicalUrl = $url;
428 }
429
437 public function getCanonicalUrl() {
438 return $this->mCanonicalUrl;
439 }
440
448 function addScript( $script ) {
449 $this->mScripts .= $script;
450 }
451
460 public function addScriptFile( $file, $unused = null ) {
461 if ( substr( $file, 0, 1 ) !== '/' && !preg_match( '#^[a-z]*://#i', $file ) ) {
462 // This is not an absolute path, protocol-relative url, or full scheme url,
463 // presumed to be an old call intended to include a file from /w/skins/common,
464 // which doesn't exist anymore as of MediaWiki 1.24 per T71277. Ignore.
465 wfDeprecated( __METHOD__, '1.24' );
466 return;
467 }
468 $this->addScript( Html::linkedScript( $file, $this->getCSPNonce() ) );
469 }
470
477 public function addInlineScript( $script ) {
478 $this->mScripts .= Html::inlineScript( "\n$script\n", $this->getCSPNonce() ) . "\n";
479 }
480
489 protected function filterModules( array $modules, $position = null,
491 ) {
493 $filteredModules = [];
494 foreach ( $modules as $val ) {
495 $module = $resourceLoader->getModule( $val );
496 if ( $module instanceof ResourceLoaderModule
497 && $module->getOrigin() <= $this->getAllowedModules( $type )
498 ) {
499 if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
500 $this->warnModuleTargetFilter( $module->getName() );
501 continue;
502 }
503 $filteredModules[] = $val;
504 }
505 }
506 return $filteredModules;
507 }
508
509 private function warnModuleTargetFilter( $moduleName ) {
510 static $warnings = [];
511 if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
512 return;
513 }
514 $warnings[$this->mTarget][$moduleName] = true;
515 $this->getResourceLoader()->getLogger()->debug(
516 'Module "{module}" not loadable on target "{target}".',
517 [
518 'module' => $moduleName,
519 'target' => $this->mTarget,
520 ]
521 );
522 }
523
533 public function getModules( $filter = false, $position = null, $param = 'mModules',
535 ) {
536 $modules = array_values( array_unique( $this->$param ) );
537 return $filter
538 ? $this->filterModules( $modules, null, $type )
539 : $modules;
540 }
541
547 public function addModules( $modules ) {
548 $this->mModules = array_merge( $this->mModules, (array)$modules );
549 }
550
555 public function getModuleScripts() {
556 wfDeprecated( __METHOD__, '1.33' );
557 return [];
558 }
559
567 public function getModuleStyles( $filter = false, $position = null ) {
568 return $this->getModules( $filter, null, 'mModuleStyles',
570 );
571 }
572
582 public function addModuleStyles( $modules ) {
583 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
584 }
585
589 public function getTarget() {
590 return $this->mTarget;
591 }
592
598 public function setTarget( $target ) {
599 $this->mTarget = $target;
600 }
601
609 public function addContentOverride( LinkTarget $target, Content $content ) {
610 if ( !$this->contentOverrides ) {
611 // Register a callback for $this->contentOverrides on the first call
612 $this->addContentOverrideCallback( function ( LinkTarget $target ) {
613 $key = $target->getNamespace() . ':' . $target->getDBkey();
614 return $this->contentOverrides[$key] ?? null;
615 } );
616 }
617
618 $key = $target->getNamespace() . ':' . $target->getDBkey();
619 $this->contentOverrides[$key] = $content;
620 }
621
629 public function addContentOverrideCallback( callable $callback ) {
630 $this->contentOverrideCallbacks[] = $callback;
631 }
632
638 function getHeadItemsArray() {
639 return $this->mHeadItems;
640 }
641
654 public function addHeadItem( $name, $value ) {
655 $this->mHeadItems[$name] = $value;
656 }
657
664 public function addHeadItems( $values ) {
665 $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
666 }
667
674 public function hasHeadItem( $name ) {
675 return isset( $this->mHeadItems[$name] );
676 }
677
684 public function addBodyClasses( $classes ) {
685 $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
686 }
687
695 public function setArticleBodyOnly( $only ) {
696 $this->mArticleBodyOnly = $only;
697 }
698
704 public function getArticleBodyOnly() {
705 return $this->mArticleBodyOnly;
706 }
707
715 public function setProperty( $name, $value ) {
716 $this->mProperties[$name] = $value;
717 }
718
726 public function getProperty( $name ) {
727 return $this->mProperties[$name] ?? null;
728 }
729
741 public function checkLastModified( $timestamp ) {
742 if ( !$timestamp || $timestamp == '19700101000000' ) {
743 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
744 return false;
745 }
746 $config = $this->getConfig();
747 if ( !$config->get( 'CachePages' ) ) {
748 wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
749 return false;
750 }
751
752 $timestamp = wfTimestamp( TS_MW, $timestamp );
753 $modifiedTimes = [
754 'page' => $timestamp,
755 'user' => $this->getUser()->getTouched(),
756 'epoch' => $config->get( 'CacheEpoch' )
757 ];
758 if ( $config->get( 'UseSquid' ) ) {
759 $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $this->getCdnCacheEpoch(
760 time(),
761 $config->get( 'SquidMaxage' )
762 ) );
763 }
764 Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
765
766 $maxModified = max( $modifiedTimes );
767 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
768
769 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
770 if ( $clientHeader === false ) {
771 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
772 return false;
773 }
774
775 # IE sends sizes after the date like this:
776 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
777 # this breaks strtotime().
778 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
779
780 Wikimedia\suppressWarnings(); // E_STRICT system time warnings
781 $clientHeaderTime = strtotime( $clientHeader );
782 Wikimedia\restoreWarnings();
783 if ( !$clientHeaderTime ) {
784 wfDebug( __METHOD__
785 . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
786 return false;
787 }
788 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
789
790 # Make debug info
791 $info = '';
792 foreach ( $modifiedTimes as $name => $value ) {
793 if ( $info !== '' ) {
794 $info .= ', ';
795 }
796 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
797 }
798
799 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
800 wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
801 wfDebug( __METHOD__ . ": effective Last-Modified: " .
802 wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
803 if ( $clientHeaderTime < $maxModified ) {
804 wfDebug( __METHOD__ . ": STALE, $info", 'private' );
805 return false;
806 }
807
808 # Not modified
809 # Give a 304 Not Modified response code and disable body output
810 wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
811 ini_set( 'zlib.output_compression', 0 );
812 $this->getRequest()->response()->statusHeader( 304 );
813 $this->sendCacheControl();
814 $this->disable();
815
816 // Don't output a compressed blob when using ob_gzhandler;
817 // it's technically against HTTP spec and seems to confuse
818 // Firefox when the response gets split over two packets.
820
821 return true;
822 }
823
829 private function getCdnCacheEpoch( $reqTime, $maxAge ) {
830 // Ensure Last-Modified is never more than (wgSquidMaxage) in the past,
831 // because even if the wiki page content hasn't changed since, static
832 // resources may have changed (skin HTML, interface messages, urls, etc.)
833 // and must roll-over in a timely manner (T46570)
834 return $reqTime - $maxAge;
835 }
836
843 public function setLastModified( $timestamp ) {
844 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
845 }
846
855 public function setRobotPolicy( $policy ) {
856 $policy = Article::formatRobotPolicy( $policy );
857
858 if ( isset( $policy['index'] ) ) {
859 $this->setIndexPolicy( $policy['index'] );
860 }
861 if ( isset( $policy['follow'] ) ) {
862 $this->setFollowPolicy( $policy['follow'] );
863 }
864 }
865
873 public function setIndexPolicy( $policy ) {
874 $policy = trim( $policy );
875 if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
876 $this->mIndexPolicy = $policy;
877 }
878 }
879
887 public function setFollowPolicy( $policy ) {
888 $policy = trim( $policy );
889 if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
890 $this->mFollowPolicy = $policy;
891 }
892 }
893
900 public function setHTMLTitle( $name ) {
901 if ( $name instanceof Message ) {
902 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
903 } else {
904 $this->mHTMLtitle = $name;
905 }
906 }
907
913 public function getHTMLTitle() {
914 return $this->mHTMLtitle;
915 }
916
922 public function setRedirectedFrom( $t ) {
923 $this->mRedirectedFrom = $t;
924 }
925
938 public function setPageTitle( $name ) {
939 if ( $name instanceof Message ) {
940 $name = $name->setContext( $this->getContext() )->text();
941 }
942
943 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
944 # but leave "<i>foobar</i>" alone
945 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
946 $this->mPageTitle = $nameWithTags;
947
948 # change "<i>foo&amp;bar</i>" to "foo&bar"
949 $this->setHTMLTitle(
950 $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
951 ->inContentLanguage()
952 );
953 }
954
960 public function getPageTitle() {
961 return $this->mPageTitle;
962 }
963
971 public function setDisplayTitle( $html ) {
972 $this->displayTitle = $html;
973 }
974
983 public function getDisplayTitle() {
984 $html = $this->displayTitle;
985 if ( $html === null ) {
986 $html = $this->getTitle()->getPrefixedText();
987 }
988
989 return Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $html ) );
990 }
991
998 public function getUnprefixedDisplayTitle() {
999 $text = $this->getDisplayTitle();
1000 $nsPrefix = $this->getTitle()->getNsText() . ':';
1001 $prefix = preg_quote( $nsPrefix, '/' );
1002
1003 return preg_replace( "/^$prefix/i", '', $text );
1004 }
1005
1011 public function setTitle( Title $t ) {
1012 $this->getContext()->setTitle( $t );
1013 }
1014
1020 public function setSubtitle( $str ) {
1021 $this->clearSubtitle();
1022 $this->addSubtitle( $str );
1023 }
1024
1030 public function addSubtitle( $str ) {
1031 if ( $str instanceof Message ) {
1032 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1033 } else {
1034 $this->mSubtitle[] = $str;
1035 }
1036 }
1037
1046 public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1047 if ( $title->isRedirect() ) {
1048 $query['redirect'] = 'no';
1049 }
1050 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1051 return wfMessage( 'backlinksubtitle' )
1052 ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1053 }
1054
1061 public function addBacklinkSubtitle( Title $title, $query = [] ) {
1062 $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1063 }
1064
1068 public function clearSubtitle() {
1069 $this->mSubtitle = [];
1070 }
1071
1077 public function getSubtitle() {
1078 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1079 }
1080
1085 public function setPrintable() {
1086 $this->mPrintable = true;
1087 }
1088
1094 public function isPrintable() {
1095 return $this->mPrintable;
1096 }
1097
1101 public function disable() {
1102 $this->mDoNothing = true;
1103 }
1104
1110 public function isDisabled() {
1111 return $this->mDoNothing;
1112 }
1113
1119 public function showNewSectionLink() {
1120 return $this->mNewSectionLink;
1121 }
1122
1128 public function forceHideNewSectionLink() {
1129 return $this->mHideNewSectionLink;
1130 }
1131
1140 public function setSyndicated( $show = true ) {
1141 if ( $show ) {
1142 $this->setFeedAppendQuery( false );
1143 } else {
1144 $this->mFeedLinks = [];
1145 }
1146 }
1147
1154 protected function getAdvertisedFeedTypes() {
1155 if ( $this->getConfig()->get( 'Feed' ) ) {
1156 return $this->getConfig()->get( 'AdvertisedFeedTypes' );
1157 } else {
1158 return [];
1159 }
1160 }
1161
1171 public function setFeedAppendQuery( $val ) {
1172 $this->mFeedLinks = [];
1173
1174 foreach ( $this->getAdvertisedFeedTypes() as $type ) {
1175 $query = "feed=$type";
1176 if ( is_string( $val ) ) {
1177 $query .= '&' . $val;
1178 }
1179 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1180 }
1181 }
1182
1189 public function addFeedLink( $format, $href ) {
1190 if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
1191 $this->mFeedLinks[$format] = $href;
1192 }
1193 }
1194
1199 public function isSyndicated() {
1200 return count( $this->mFeedLinks ) > 0;
1201 }
1202
1207 public function getSyndicationLinks() {
1208 return $this->mFeedLinks;
1209 }
1210
1216 public function getFeedAppendQuery() {
1217 return $this->mFeedLinksAppendQuery;
1218 }
1219
1227 public function setArticleFlag( $newVal ) {
1228 $this->mIsArticle = $newVal;
1229 if ( $newVal ) {
1230 $this->mIsArticleRelated = $newVal;
1231 }
1232 }
1233
1240 public function isArticle() {
1241 return $this->mIsArticle;
1242 }
1243
1250 public function setArticleRelated( $newVal ) {
1251 $this->mIsArticleRelated = $newVal;
1252 if ( !$newVal ) {
1253 $this->mIsArticle = false;
1254 }
1255 }
1256
1262 public function isArticleRelated() {
1263 return $this->mIsArticleRelated;
1264 }
1265
1271 public function setCopyright( $hasCopyright ) {
1272 $this->mHasCopyright = $hasCopyright;
1273 }
1274
1284 public function showsCopyright() {
1285 return $this->isArticle() || $this->mHasCopyright;
1286 }
1287
1294 public function addLanguageLinks( array $newLinkArray ) {
1295 $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $newLinkArray );
1296 }
1297
1304 public function setLanguageLinks( array $newLinkArray ) {
1305 $this->mLanguageLinks = $newLinkArray;
1306 }
1307
1313 public function getLanguageLinks() {
1314 return $this->mLanguageLinks;
1315 }
1316
1322 public function addCategoryLinks( array $categories ) {
1323 if ( !$categories ) {
1324 return;
1325 }
1326
1327 $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1328
1329 # Set all the values to 'normal'.
1330 $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1331
1332 # Mark hidden categories
1333 foreach ( $res as $row ) {
1334 if ( isset( $row->pp_value ) ) {
1335 $categories[$row->page_title] = 'hidden';
1336 }
1337 }
1338
1339 // Avoid PHP 7.1 warning of passing $this by reference
1340 $outputPage = $this;
1341 # Add the remaining categories to the skin
1342 if ( Hooks::run(
1343 'OutputPageMakeCategoryLinks',
1344 [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1345 ) {
1346 $services = MediaWikiServices::getInstance();
1347 $linkRenderer = $services->getLinkRenderer();
1348 foreach ( $categories as $category => $type ) {
1349 // array keys will cast numeric category names to ints, so cast back to string
1350 $category = (string)$category;
1351 $origcategory = $category;
1352 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1353 if ( !$title ) {
1354 continue;
1355 }
1356 $services->getContentLanguage()->findVariantLink( $category, $title, true );
1357 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1358 continue;
1359 }
1360 $text = $services->getContentLanguage()->convertHtml( $title->getText() );
1361 $this->mCategories[$type][] = $title->getText();
1362 $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1363 }
1364 }
1365 }
1366
1371 protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1372 # Add the links to a LinkBatch
1373 $arr = [ NS_CATEGORY => $categories ];
1374 $lb = new LinkBatch;
1375 $lb->setArray( $arr );
1376
1377 # Fetch existence plus the hiddencat property
1378 $dbr = wfGetDB( DB_REPLICA );
1379 $fields = array_merge(
1380 LinkCache::getSelectFields(),
1381 [ 'page_namespace', 'page_title', 'pp_value' ]
1382 );
1383
1384 $res = $dbr->select( [ 'page', 'page_props' ],
1385 $fields,
1386 $lb->constructSet( 'page', $dbr ),
1387 __METHOD__,
1388 [],
1389 [ 'page_props' => [ 'LEFT JOIN', [
1390 'pp_propname' => 'hiddencat',
1391 'pp_page = page_id'
1392 ] ] ]
1393 );
1394
1395 # Add the results to the link cache
1396 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1397 $lb->addResultToCache( $linkCache, $res );
1398
1399 return $res;
1400 }
1401
1407 public function setCategoryLinks( array $categories ) {
1408 $this->mCategoryLinks = [];
1409 $this->addCategoryLinks( $categories );
1410 }
1411
1420 public function getCategoryLinks() {
1421 return $this->mCategoryLinks;
1422 }
1423
1433 public function getCategories( $type = 'all' ) {
1434 if ( $type === 'all' ) {
1435 $allCategories = [];
1436 foreach ( $this->mCategories as $categories ) {
1437 $allCategories = array_merge( $allCategories, $categories );
1438 }
1439 return $allCategories;
1440 }
1441 if ( !isset( $this->mCategories[$type] ) ) {
1442 throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1443 }
1444 return $this->mCategories[$type];
1445 }
1446
1456 public function setIndicators( array $indicators ) {
1457 $this->mIndicators = $indicators + $this->mIndicators;
1458 // Keep ordered by key
1459 ksort( $this->mIndicators );
1460 }
1461
1470 public function getIndicators() {
1471 return $this->mIndicators;
1472 }
1473
1482 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1483 $this->addModuleStyles( 'mediawiki.helplink' );
1484 $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1485
1486 if ( $overrideBaseUrl ) {
1487 $helpUrl = $to;
1488 } else {
1489 $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1490 $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1491 }
1492
1493 $link = Html::rawElement(
1494 'a',
1495 [
1496 'href' => $helpUrl,
1497 'target' => '_blank',
1498 'class' => 'mw-helplink',
1499 ],
1500 $text
1501 );
1502
1503 $this->setIndicators( [ 'mw-helplink' => $link ] );
1504 }
1505
1514 public function disallowUserJs() {
1515 $this->reduceAllowedModules(
1518 );
1519
1520 // Site-wide styles are controlled by a config setting, see T73621
1521 // for background on why. User styles are never allowed.
1522 if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1524 } else {
1526 }
1527 $this->reduceAllowedModules(
1529 $styleOrigin
1530 );
1531 }
1532
1539 public function getAllowedModules( $type ) {
1541 return min( array_values( $this->mAllowedModules ) );
1542 } else {
1543 return $this->mAllowedModules[$type] ?? ResourceLoaderModule::ORIGIN_ALL;
1544 }
1545 }
1546
1556 public function reduceAllowedModules( $type, $level ) {
1557 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1558 }
1559
1565 public function prependHTML( $text ) {
1566 $this->mBodytext = $text . $this->mBodytext;
1567 }
1568
1574 public function addHTML( $text ) {
1575 $this->mBodytext .= $text;
1576 }
1577
1587 public function addElement( $element, array $attribs = [], $contents = '' ) {
1588 $this->addHTML( Html::element( $element, $attribs, $contents ) );
1589 }
1590
1594 public function clearHTML() {
1595 $this->mBodytext = '';
1596 }
1597
1603 public function getHTML() {
1604 return $this->mBodytext;
1605 }
1606
1614 public function parserOptions( $options = null ) {
1615 if ( $options !== null ) {
1616 wfDeprecated( __METHOD__ . ' with non-null $options', '1.31' );
1617 }
1618
1619 if ( $options !== null && !empty( $options->isBogus ) ) {
1620 // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1621 // been changed somehow, and keep it if so.
1622 $anonPO = ParserOptions::newFromAnon();
1623 $anonPO->setAllowUnsafeRawHtml( false );
1624 if ( !$options->matches( $anonPO ) ) {
1625 wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1626 $options->isBogus = false;
1627 }
1628 }
1629
1630 if ( !$this->mParserOptions ) {
1631 if ( !$this->getUser()->isSafeToLoad() ) {
1632 // $wgUser isn't unstubbable yet, so don't try to get a
1633 // ParserOptions for it. And don't cache this ParserOptions
1634 // either.
1635 $po = ParserOptions::newFromAnon();
1636 $po->setAllowUnsafeRawHtml( false );
1637 $po->isBogus = true;
1638 if ( $options !== null ) {
1639 $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1640 }
1641 return $po;
1642 }
1643
1644 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1645 $this->mParserOptions->setAllowUnsafeRawHtml( false );
1646 }
1647
1648 if ( $options !== null && !empty( $options->isBogus ) ) {
1649 // They're trying to restore the bogus pre-$wgUser PO. Do the right
1650 // thing.
1651 return wfSetVar( $this->mParserOptions, null, true );
1652 } else {
1653 return wfSetVar( $this->mParserOptions, $options );
1654 }
1655 }
1656
1664 public function setRevisionId( $revid ) {
1665 $val = is_null( $revid ) ? null : intval( $revid );
1666 return wfSetVar( $this->mRevisionId, $val, true );
1667 }
1668
1674 public function getRevisionId() {
1675 return $this->mRevisionId;
1676 }
1677
1685 public function setRevisionTimestamp( $timestamp ) {
1686 return wfSetVar( $this->mRevisionTimestamp, $timestamp, true );
1687 }
1688
1695 public function getRevisionTimestamp() {
1696 return $this->mRevisionTimestamp;
1697 }
1698
1705 public function setFileVersion( $file ) {
1706 $val = null;
1707 if ( $file instanceof File && $file->exists() ) {
1708 $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1709 }
1710 return wfSetVar( $this->mFileVersion, $val, true );
1711 }
1712
1718 public function getFileVersion() {
1719 return $this->mFileVersion;
1720 }
1721
1728 public function getTemplateIds() {
1729 return $this->mTemplateIds;
1730 }
1731
1738 public function getFileSearchOptions() {
1739 return $this->mImageTimeKeys;
1740 }
1741
1754 public function addWikiText( $text, $linestart = true, $interface = true ) {
1755 wfDeprecated( __METHOD__, '1.32' );
1756 $title = $this->getTitle();
1757 if ( !$title ) {
1758 throw new MWException( 'Title is null' );
1759 }
1760 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/false, $interface );
1761 }
1762
1779 public function addWikiTextAsInterface(
1780 $text, $linestart = true, Title $title = null
1781 ) {
1782 if ( $title === null ) {
1783 $title = $this->getTitle();
1784 }
1785 if ( !$title ) {
1786 throw new MWException( 'Title is null' );
1787 }
1788 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/true );
1789 }
1790
1805 $wrapperClass, $text
1806 ) {
1808 $text, $this->getTitle(),
1809 /*linestart*/true, /*tidy*/true, /*interface*/true,
1810 $wrapperClass
1811 );
1812 }
1813
1829 public function addWikiTextAsContent(
1830 $text, $linestart = true, Title $title = null
1831 ) {
1832 if ( $title === null ) {
1833 $title = $this->getTitle();
1834 }
1835 if ( !$title ) {
1836 throw new MWException( 'Title is null' );
1837 }
1838 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1839 }
1840
1850 public function addWikiTextWithTitle( $text, Title $title, $linestart = true ) {
1851 wfDeprecated( __METHOD__, '1.32' );
1852 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/false, /*interface*/false );
1853 }
1854
1865 function addWikiTextTitleTidy( $text, Title $title, $linestart = true ) {
1866 wfDeprecated( __METHOD__, '1.32' );
1867 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1868 }
1869
1878 public function addWikiTextTidy( $text, $linestart = true ) {
1879 wfDeprecated( __METHOD__, '1.32' );
1880 $title = $this->getTitle();
1881 if ( !$title ) {
1882 throw new MWException( 'Title is null' );
1883 }
1884 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1885 }
1886
1906 public function addWikiTextTitle( $text, Title $title, $linestart,
1907 $tidy = false, $interface = false
1908 ) {
1909 wfDeprecated( __METHOD__, '1.32' );
1910 return $this->addWikiTextTitleInternal( $text, $title, $linestart, $tidy, $interface );
1911 }
1912
1930 $text, Title $title, $linestart, $tidy, $interface, $wrapperClass = null
1931 ) {
1932 if ( !$tidy ) {
1933 wfDeprecated( 'disabling tidy', '1.32' );
1934 }
1935
1936 $parserOutput = $this->parseInternal(
1937 $text, $title, $linestart, $tidy, $interface, /*language*/null
1938 );
1939
1940 $this->addParserOutput( $parserOutput, [
1941 'enableSectionEditLinks' => false,
1942 'wrapperDivClass' => $wrapperClass ?? '',
1943 ] );
1944 }
1945
1954 public function addParserOutputMetadata( ParserOutput $parserOutput ) {
1955 $this->mLanguageLinks =
1956 array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
1957 $this->addCategoryLinks( $parserOutput->getCategories() );
1958 $this->setIndicators( $parserOutput->getIndicators() );
1959 $this->mNewSectionLink = $parserOutput->getNewSection();
1960 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1961
1962 if ( !$parserOutput->isCacheable() ) {
1963 $this->enableClientCache( false );
1964 }
1965 $this->mNoGallery = $parserOutput->getNoGallery();
1966 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1967 $this->addModules( $parserOutput->getModules() );
1968 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1969 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1970 $this->mPreventClickjacking = $this->mPreventClickjacking
1971 || $parserOutput->preventClickjacking();
1972
1973 // Template versioning...
1974 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1975 if ( isset( $this->mTemplateIds[$ns] ) ) {
1976 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1977 } else {
1978 $this->mTemplateIds[$ns] = $dbks;
1979 }
1980 }
1981 // File versioning...
1982 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1983 $this->mImageTimeKeys[$dbk] = $data;
1984 }
1985
1986 // Hooks registered in the object
1987 $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1988 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1989 list( $hookName, $data ) = $hookInfo;
1990 if ( isset( $parserOutputHooks[$hookName] ) ) {
1991 $parserOutputHooks[$hookName]( $this, $parserOutput, $data );
1992 }
1993 }
1994
1995 // Enable OOUI if requested via ParserOutput
1996 if ( $parserOutput->getEnableOOUI() ) {
1997 $this->enableOOUI();
1998 }
1999
2000 // Include parser limit report
2001 if ( !$this->limitReportJSData ) {
2002 $this->limitReportJSData = $parserOutput->getLimitReportJSData();
2003 }
2004
2005 // Link flags are ignored for now, but may in the future be
2006 // used to mark individual language links.
2007 $linkFlags = [];
2008 // Avoid PHP 7.1 warning of passing $this by reference
2009 $outputPage = $this;
2010 Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
2011 Hooks::runWithoutAbort( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
2012
2013 // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
2014 // so that extensions may modify ParserOutput to toggle TOC.
2015 // This cannot be moved to addParserOutputText because that is not
2016 // called by EditPage for Preview.
2017 if ( $parserOutput->getTOCHTML() ) {
2018 $this->mEnableTOC = true;
2019 }
2020 }
2021
2030 public function addParserOutputContent( ParserOutput $parserOutput, $poOptions = [] ) {
2031 $this->addParserOutputText( $parserOutput, $poOptions );
2032
2033 $this->addModules( $parserOutput->getModules() );
2034 $this->addModuleStyles( $parserOutput->getModuleStyles() );
2035
2036 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
2037 }
2038
2046 public function addParserOutputText( ParserOutput $parserOutput, $poOptions = [] ) {
2047 $text = $parserOutput->getText( $poOptions );
2048 // Avoid PHP 7.1 warning of passing $this by reference
2049 $outputPage = $this;
2050 Hooks::runWithoutAbort( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
2051 $this->addHTML( $text );
2052 }
2053
2060 function addParserOutput( ParserOutput $parserOutput, $poOptions = [] ) {
2061 $this->addParserOutputMetadata( $parserOutput );
2062 $this->addParserOutputText( $parserOutput, $poOptions );
2063 }
2064
2070 public function addTemplate( &$template ) {
2071 $this->addHTML( $template->getHTML() );
2072 }
2073
2092 public function parse( $text, $linestart = true, $interface = false, $language = null ) {
2093 wfDeprecated( __METHOD__, '1.33' );
2094 return $this->parseInternal(
2095 $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, $language
2096 )->getText( [
2097 'enableSectionEditLinks' => false,
2098 ] );
2099 }
2100
2112 public function parseAsContent( $text, $linestart = true ) {
2113 return $this->parseInternal(
2114 $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/false, /*language*/null
2115 )->getText( [
2116 'enableSectionEditLinks' => false,
2117 'wrapperDivClass' => ''
2118 ] );
2119 }
2120
2133 public function parseAsInterface( $text, $linestart = true ) {
2134 return $this->parseInternal(
2135 $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/true, /*language*/null
2136 )->getText( [
2137 'enableSectionEditLinks' => false,
2138 'wrapperDivClass' => ''
2139 ] );
2140 }
2141
2156 public function parseInlineAsInterface( $text, $linestart = true ) {
2157 return Parser::stripOuterParagraph(
2158 $this->parseAsInterface( $text, $linestart )
2159 );
2160 }
2161
2175 public function parseInline( $text, $linestart = true, $interface = false ) {
2176 wfDeprecated( __METHOD__, '1.33' );
2177 $parsed = $this->parseInternal(
2178 $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, /*language*/null
2179 )->getText( [
2180 'enableSectionEditLinks' => false,
2181 'wrapperDivClass' => '', /* no wrapper div */
2182 ] );
2183 return Parser::stripOuterParagraph( $parsed );
2184 }
2185
2200 private function parseInternal( $text, $title, $linestart, $tidy, $interface, $language ) {
2201 global $wgParser;
2202
2203 if ( is_null( $title ) ) {
2204 throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
2205 }
2206
2207 $popts = $this->parserOptions();
2208 $oldTidy = $popts->setTidy( $tidy );
2209 $oldInterface = $popts->setInterfaceMessage( (bool)$interface );
2210
2211 if ( $language !== null ) {
2212 $oldLang = $popts->setTargetLanguage( $language );
2213 }
2214
2215 $parserOutput = $wgParser->getFreshParser()->parse(
2216 $text, $title, $popts,
2217 $linestart, true, $this->mRevisionId
2218 );
2219
2220 $popts->setTidy( $oldTidy );
2221 $popts->setInterfaceMessage( $oldInterface );
2222
2223 if ( $language !== null ) {
2224 $popts->setTargetLanguage( $oldLang );
2225 }
2226
2227 return $parserOutput;
2228 }
2229
2235 public function setCdnMaxage( $maxage ) {
2236 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2237 }
2238
2248 public function lowerCdnMaxage( $maxage ) {
2249 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2250 $this->setCdnMaxage( $this->mCdnMaxage );
2251 }
2252
2265 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2266 $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2267 $maxTTL = $maxTTL ?: $this->getConfig()->get( 'SquidMaxage' );
2268
2269 if ( $mtime === null || $mtime === false ) {
2270 return $minTTL; // entity does not exist
2271 }
2272
2273 $age = MWTimestamp::time() - wfTimestamp( TS_UNIX, $mtime );
2274 $adaptiveTTL = max( 0.9 * $age, $minTTL );
2275 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2276
2277 $this->lowerCdnMaxage( (int)$adaptiveTTL );
2278 }
2279
2287 public function enableClientCache( $state ) {
2288 return wfSetVar( $this->mEnableClientCache, $state );
2289 }
2290
2297 if ( self::$cacheVaryCookies === null ) {
2298 $config = $this->getConfig();
2299 self::$cacheVaryCookies = array_values( array_unique( array_merge(
2300 SessionManager::singleton()->getVaryCookies(),
2301 [
2302 'forceHTTPS',
2303 ],
2304 $config->get( 'CacheVaryCookies' )
2305 ) ) );
2306 Hooks::run( 'GetCacheVaryCookies', [ $this, &self::$cacheVaryCookies ] );
2307 }
2308 return self::$cacheVaryCookies;
2309 }
2310
2318 $request = $this->getRequest();
2319 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2320 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2321 wfDebug( __METHOD__ . ": found $cookieName\n" );
2322 return true;
2323 }
2324 }
2325 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2326 return false;
2327 }
2328
2337 public function addVaryHeader( $header, array $option = null ) {
2338 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2339 $this->mVaryHeader[$header] = [];
2340 }
2341 if ( !is_array( $option ) ) {
2342 $option = [];
2343 }
2344 $this->mVaryHeader[$header] =
2345 array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
2346 }
2347
2354 public function getVaryHeader() {
2355 // If we vary on cookies, let's make sure it's always included here too.
2356 if ( $this->getCacheVaryCookies() ) {
2357 $this->addVaryHeader( 'Cookie' );
2358 }
2359
2360 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2361 $this->addVaryHeader( $header, $options );
2362 }
2363 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2364 }
2365
2371 public function addLinkHeader( $header ) {
2372 $this->mLinkHeader[] = $header;
2373 }
2374
2380 public function getLinkHeader() {
2381 if ( !$this->mLinkHeader ) {
2382 return false;
2383 }
2384
2385 return 'Link: ' . implode( ',', $this->mLinkHeader );
2386 }
2387
2395 public function getKeyHeader() {
2396 wfDeprecated( '$wgUseKeyHeader', '1.32' );
2397
2398 $cvCookies = $this->getCacheVaryCookies();
2399
2400 $cookiesOption = [];
2401 foreach ( $cvCookies as $cookieName ) {
2402 $cookiesOption[] = 'param=' . $cookieName;
2403 }
2404 $this->addVaryHeader( 'Cookie', $cookiesOption );
2405
2406 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2407 $this->addVaryHeader( $header, $options );
2408 }
2409
2410 $headers = [];
2411 foreach ( $this->mVaryHeader as $header => $option ) {
2412 $newheader = $header;
2413 if ( is_array( $option ) && count( $option ) > 0 ) {
2414 $newheader .= ';' . implode( ';', $option );
2415 }
2416 $headers[] = $newheader;
2417 }
2418 $key = 'Key: ' . implode( ',', $headers );
2419
2420 return $key;
2421 }
2422
2430 private function addAcceptLanguage() {
2431 $title = $this->getTitle();
2432 if ( !$title instanceof Title ) {
2433 return;
2434 }
2435
2436 $lang = $title->getPageLanguage();
2437 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2438 $variants = $lang->getVariants();
2439 $aloption = [];
2440 foreach ( $variants as $variant ) {
2441 if ( $variant === $lang->getCode() ) {
2442 continue;
2443 }
2444
2445 // XXX Note that this code is not strictly correct: we
2446 // do a case-insensitive match in
2447 // LanguageConverter::getHeaderVariant() while the
2448 // (abandoned, draft) spec for the `Key` header only
2449 // allows case-sensitive matches. To match the logic
2450 // in LanguageConverter::getHeaderVariant() we should
2451 // also be looking at fallback variants and deprecated
2452 // mediawiki-internal codes, as well as BCP 47
2453 // normalized forms.
2454
2455 $aloption[] = "substr=$variant";
2456
2457 // IE and some other browsers use BCP 47 standards in their Accept-Language header,
2458 // like "zh-CN" or "zh-Hant". We should handle these too.
2459 $variantBCP47 = LanguageCode::bcp47( $variant );
2460 if ( $variantBCP47 !== $variant ) {
2461 $aloption[] = "substr=$variantBCP47";
2462 }
2463 }
2464 $this->addVaryHeader( 'Accept-Language', $aloption );
2465 }
2466 }
2467
2478 public function preventClickjacking( $enable = true ) {
2479 $this->mPreventClickjacking = $enable;
2480 }
2481
2487 public function allowClickjacking() {
2488 $this->mPreventClickjacking = false;
2489 }
2490
2497 public function getPreventClickjacking() {
2498 return $this->mPreventClickjacking;
2499 }
2500
2508 public function getFrameOptions() {
2509 $config = $this->getConfig();
2510 if ( $config->get( 'BreakFrames' ) ) {
2511 return 'DENY';
2512 } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2513 return $config->get( 'EditPageFrameOptions' );
2514 }
2515 return false;
2516 }
2517
2524 private function getOriginTrials() {
2525 $config = $this->getConfig();
2526
2527 return $config->get( 'OriginTrials' );
2528 }
2529
2533 public function sendCacheControl() {
2534 $response = $this->getRequest()->response();
2535 $config = $this->getConfig();
2536
2537 $this->addVaryHeader( 'Cookie' );
2538 $this->addAcceptLanguage();
2539
2540 # don't serve compressed data to clients who can't handle it
2541 # maintain different caches for logged-in users and non-logged in ones
2542 $response->header( $this->getVaryHeader() );
2543
2544 if ( $config->get( 'UseKeyHeader' ) ) {
2545 $response->header( $this->getKeyHeader() );
2546 }
2547
2548 if ( $this->mEnableClientCache ) {
2549 if (
2550 $config->get( 'UseSquid' ) &&
2551 !$response->hasCookies() &&
2552 !SessionManager::getGlobalSession()->isPersistent() &&
2553 !$this->isPrintable() &&
2554 $this->mCdnMaxage != 0 &&
2555 !$this->haveCacheVaryCookies()
2556 ) {
2557 if ( $config->get( 'UseESI' ) ) {
2558 wfDeprecated( '$wgUseESI = true', '1.33' );
2559 # We'll purge the proxy cache explicitly, but require end user agents
2560 # to revalidate against the proxy on each visit.
2561 # Surrogate-Control controls our CDN, Cache-Control downstream caches
2562 wfDebug( __METHOD__ .
2563 ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2564 # start with a shorter timeout for initial testing
2565 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2566 $response->header(
2567 "Surrogate-Control: max-age={$config->get( 'SquidMaxage' )}" .
2568 "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
2569 );
2570 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2571 } else {
2572 # We'll purge the proxy cache for anons explicitly, but require end user agents
2573 # to revalidate against the proxy on each visit.
2574 # IMPORTANT! The CDN needs to replace the Cache-Control header with
2575 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2576 wfDebug( __METHOD__ .
2577 ": local proxy caching; {$this->mLastModified} **", 'private' );
2578 # start with a shorter timeout for initial testing
2579 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2580 $response->header( "Cache-Control: " .
2581 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2582 }
2583 } else {
2584 # We do want clients to cache if they can, but they *must* check for updates
2585 # on revisiting the page.
2586 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2587 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2588 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2589 }
2590 if ( $this->mLastModified ) {
2591 $response->header( "Last-Modified: {$this->mLastModified}" );
2592 }
2593 } else {
2594 wfDebug( __METHOD__ . ": no caching **", 'private' );
2595
2596 # In general, the absence of a last modified header should be enough to prevent
2597 # the client from using its cache. We send a few other things just to make sure.
2598 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2599 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2600 $response->header( 'Pragma: no-cache' );
2601 }
2602 }
2603
2609 public function loadSkinModules( $sk ) {
2610 foreach ( $sk->getDefaultModules() as $group => $modules ) {
2611 if ( $group === 'styles' ) {
2612 foreach ( $modules as $key => $moduleMembers ) {
2613 $this->addModuleStyles( $moduleMembers );
2614 }
2615 } else {
2616 $this->addModules( $modules );
2617 }
2618 }
2619 }
2620
2631 public function output( $return = false ) {
2632 if ( $this->mDoNothing ) {
2633 return $return ? '' : null;
2634 }
2635
2636 $response = $this->getRequest()->response();
2637 $config = $this->getConfig();
2638
2639 if ( $this->mRedirect != '' ) {
2640 # Standards require redirect URLs to be absolute
2641 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2642
2643 $redirect = $this->mRedirect;
2644 $code = $this->mRedirectCode;
2645
2646 if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2647 if ( $code == '301' || $code == '303' ) {
2648 if ( !$config->get( 'DebugRedirects' ) ) {
2649 $response->statusHeader( $code );
2650 }
2651 $this->mLastModified = wfTimestamp( TS_RFC2822 );
2652 }
2653 if ( $config->get( 'VaryOnXFP' ) ) {
2654 $this->addVaryHeader( 'X-Forwarded-Proto' );
2655 }
2656 $this->sendCacheControl();
2657
2658 $response->header( "Content-Type: text/html; charset=utf-8" );
2659 if ( $config->get( 'DebugRedirects' ) ) {
2660 $url = htmlspecialchars( $redirect );
2661 print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2662 print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2663 print "</body>\n</html>\n";
2664 } else {
2665 $response->header( 'Location: ' . $redirect );
2666 }
2667 }
2668
2669 return $return ? '' : null;
2670 } elseif ( $this->mStatusCode ) {
2671 $response->statusHeader( $this->mStatusCode );
2672 }
2673
2674 # Buffer output; final headers may depend on later processing
2675 ob_start();
2676
2677 $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2678 $response->header( 'Content-language: ' .
2679 MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
2680
2681 if ( !$this->mArticleBodyOnly ) {
2682 $sk = $this->getSkin();
2683 }
2684
2685 $linkHeader = $this->getLinkHeader();
2686 if ( $linkHeader ) {
2687 $response->header( $linkHeader );
2688 }
2689
2690 // Prevent framing, if requested
2691 $frameOptions = $this->getFrameOptions();
2692 if ( $frameOptions ) {
2693 $response->header( "X-Frame-Options: $frameOptions" );
2694 }
2695
2696 $originTrials = $this->getOriginTrials();
2697 foreach ( $originTrials as $originTrial ) {
2698 $response->header( "Origin-Trial: $originTrial", false );
2699 }
2700
2702
2703 if ( $this->mArticleBodyOnly ) {
2704 echo $this->mBodytext;
2705 } else {
2706 // Enable safe mode if requested (T152169)
2707 if ( $this->getRequest()->getBool( 'safemode' ) ) {
2708 $this->disallowUserJs();
2709 }
2710
2711 $sk = $this->getSkin();
2712 $this->loadSkinModules( $sk );
2713
2714 MWDebug::addModules( $this );
2715
2716 // Avoid PHP 7.1 warning of passing $this by reference
2717 $outputPage = $this;
2718 // Hook that allows last minute changes to the output page, e.g.
2719 // adding of CSS or Javascript by extensions.
2720 Hooks::runWithoutAbort( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2721
2722 try {
2723 $sk->outputPage();
2724 } catch ( Exception $e ) {
2725 ob_end_clean(); // bug T129657
2726 throw $e;
2727 }
2728 }
2729
2730 try {
2731 // This hook allows last minute changes to final overall output by modifying output buffer
2732 Hooks::runWithoutAbort( 'AfterFinalPageOutput', [ $this ] );
2733 } catch ( Exception $e ) {
2734 ob_end_clean(); // bug T129657
2735 throw $e;
2736 }
2737
2738 $this->sendCacheControl();
2739
2740 if ( $return ) {
2741 return ob_get_clean();
2742 } else {
2743 ob_end_flush();
2744 return null;
2745 }
2746 }
2747
2758 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2759 $this->setPageTitle( $pageTitle );
2760 if ( $htmlTitle !== false ) {
2761 $this->setHTMLTitle( $htmlTitle );
2762 }
2763 $this->setRobotPolicy( 'noindex,nofollow' );
2764 $this->setArticleRelated( false );
2765 $this->enableClientCache( false );
2766 $this->mRedirect = '';
2767 $this->clearSubtitle();
2768 $this->clearHTML();
2769 }
2770
2783 public function showErrorPage( $title, $msg, $params = [] ) {
2784 if ( !$title instanceof Message ) {
2785 $title = $this->msg( $title );
2786 }
2787
2788 $this->prepareErrorPage( $title );
2789
2790 if ( $msg instanceof Message ) {
2791 if ( $params !== [] ) {
2792 trigger_error( 'Argument ignored: $params. The message parameters argument '
2793 . 'is discarded when the $msg argument is a Message object instead of '
2794 . 'a string.', E_USER_NOTICE );
2795 }
2796 $this->addHTML( $msg->parseAsBlock() );
2797 } else {
2798 $this->addWikiMsgArray( $msg, $params );
2799 }
2800
2801 $this->returnToMain();
2802 }
2803
2810 public function showPermissionsErrorPage( array $errors, $action = null ) {
2811 foreach ( $errors as $key => $error ) {
2812 $errors[$key] = (array)$error;
2813 }
2814
2815 // For some action (read, edit, create and upload), display a "login to do this action"
2816 // error if all of the following conditions are met:
2817 // 1. the user is not logged in
2818 // 2. the only error is insufficient permissions (i.e. no block or something else)
2819 // 3. the error can be avoided simply by logging in
2820 if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2821 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2822 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2823 && ( User::groupHasPermission( 'user', $action )
2824 || User::groupHasPermission( 'autoconfirmed', $action ) )
2825 ) {
2826 $displayReturnto = null;
2827
2828 # Due to T34276, if a user does not have read permissions,
2829 # $this->getTitle() will just give Special:Badtitle, which is
2830 # not especially useful as a returnto parameter. Use the title
2831 # from the request instead, if there was one.
2832 $request = $this->getRequest();
2833 $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2834 if ( $action == 'edit' ) {
2835 $msg = 'whitelistedittext';
2836 $displayReturnto = $returnto;
2837 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2838 $msg = 'nocreatetext';
2839 } elseif ( $action == 'upload' ) {
2840 $msg = 'uploadnologintext';
2841 } else { # Read
2842 $msg = 'loginreqpagetext';
2843 $displayReturnto = Title::newMainPage();
2844 }
2845
2846 $query = [];
2847
2848 if ( $returnto ) {
2849 $query['returnto'] = $returnto->getPrefixedText();
2850
2851 if ( !$request->wasPosted() ) {
2852 $returntoquery = $request->getValues();
2853 unset( $returntoquery['title'] );
2854 unset( $returntoquery['returnto'] );
2855 unset( $returntoquery['returntoquery'] );
2856 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2857 }
2858 }
2859 $title = SpecialPage::getTitleFor( 'Userlogin' );
2860 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2861 $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
2862 $loginLink = $linkRenderer->makeKnownLink(
2863 $title,
2864 $this->msg( 'loginreqlink' )->text(),
2865 [],
2866 $query
2867 );
2868
2869 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2870 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
2871
2872 # Don't return to a page the user can't read otherwise
2873 # we'll end up in a pointless loop
2874 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2875 $this->returnToMain( null, $displayReturnto );
2876 }
2877 } else {
2878 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2879 $this->addWikiTextAsInterface( $this->formatPermissionsErrorMessage( $errors, $action ) );
2880 }
2881 }
2882
2889 public function versionRequired( $version ) {
2890 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2891
2892 $this->addWikiMsg( 'versionrequiredtext', $version );
2893 $this->returnToMain();
2894 }
2895
2903 public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2904 if ( $action == null ) {
2905 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2906 } else {
2907 $action_desc = $this->msg( "action-$action" )->plain();
2908 $text = $this->msg(
2909 'permissionserrorstext-withaction',
2910 count( $errors ),
2911 $action_desc
2912 )->plain() . "\n\n";
2913 }
2914
2915 if ( count( $errors ) > 1 ) {
2916 $text .= '<ul class="permissions-errors">' . "\n";
2917
2918 foreach ( $errors as $error ) {
2919 $text .= '<li>';
2920 $text .= $this->msg( ...$error )->plain();
2921 $text .= "</li>\n";
2922 }
2923 $text .= '</ul>';
2924 } else {
2925 $text .= "<div class=\"permissions-errors\">\n" .
2926 $this->msg( ...reset( $errors ) )->plain() .
2927 "\n</div>";
2928 }
2929
2930 return $text;
2931 }
2932
2942 public function showLagWarning( $lag ) {
2943 $config = $this->getConfig();
2944 if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2945 $lag = floor( $lag ); // floor to avoid nano seconds to display
2946 $message = $lag < $config->get( 'SlaveLagCritical' )
2947 ? 'lag-warn-normal'
2948 : 'lag-warn-high';
2949 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2950 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2951 }
2952 }
2953
2960 public function showFatalError( $message ) {
2961 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2962
2963 $this->addHTML( $message );
2964 }
2965
2969 public function showUnexpectedValueError( $name, $val ) {
2970 wfDeprecated( __METHOD__, '1.32' );
2971 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->escaped() );
2972 }
2973
2977 public function showFileCopyError( $old, $new ) {
2978 wfDeprecated( __METHOD__, '1.32' );
2979 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->escaped() );
2980 }
2981
2985 public function showFileRenameError( $old, $new ) {
2986 wfDeprecated( __METHOD__, '1.32' );
2987 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->escaped() );
2988 }
2989
2993 public function showFileDeleteError( $name ) {
2994 wfDeprecated( __METHOD__, '1.32' );
2995 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->escaped() );
2996 }
2997
3001 public function showFileNotFoundError( $name ) {
3002 wfDeprecated( __METHOD__, '1.32' );
3003 $this->showFatalError( $this->msg( 'filenotfound', $name )->escaped() );
3004 }
3005
3014 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
3015 $linkRenderer = MediaWikiServices::getInstance()
3016 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
3017 $link = $this->msg( 'returnto' )->rawParams(
3018 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
3019 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
3020 }
3021
3030 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
3031 if ( $returnto == null ) {
3032 $returnto = $this->getRequest()->getText( 'returnto' );
3033 }
3034
3035 if ( $returntoquery == null ) {
3036 $returntoquery = $this->getRequest()->getText( 'returntoquery' );
3037 }
3038
3039 if ( $returnto === '' ) {
3040 $returnto = Title::newMainPage();
3041 }
3042
3043 if ( is_object( $returnto ) ) {
3044 $titleObj = $returnto;
3045 } else {
3046 $titleObj = Title::newFromText( $returnto );
3047 }
3048 // We don't want people to return to external interwiki. That
3049 // might potentially be used as part of a phishing scheme
3050 if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
3051 $titleObj = Title::newMainPage();
3052 }
3053
3054 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
3055 }
3056
3057 private function getRlClientContext() {
3058 if ( !$this->rlClientContext ) {
3059 $query = ResourceLoader::makeLoaderQuery(
3060 [], // modules; not relevant
3061 $this->getLanguage()->getCode(),
3062 $this->getSkin()->getSkinName(),
3063 $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
3064 null, // version; not relevant
3065 ResourceLoader::inDebugMode(),
3066 null, // only; not relevant
3067 $this->isPrintable(),
3068 $this->getRequest()->getBool( 'handheld' )
3069 );
3070 $this->rlClientContext = new ResourceLoaderContext(
3071 $this->getResourceLoader(),
3072 new FauxRequest( $query )
3073 );
3074 if ( $this->contentOverrideCallbacks ) {
3075 $this->rlClientContext = new DerivativeResourceLoaderContext( $this->rlClientContext );
3076 $this->rlClientContext->setContentOverrideCallback( function ( Title $title ) {
3077 foreach ( $this->contentOverrideCallbacks as $callback ) {
3078 $content = $callback( $title );
3079 if ( $content !== null ) {
3080 $text = ContentHandler::getContentText( $content );
3081 if ( strpos( $text, '</script>' ) !== false ) {
3082 // Proactively replace this so that we can display a message
3083 // to the user, instead of letting it go to Html::inlineScript(),
3084 // where it would be considered a server-side issue.
3085 $titleFormatted = $title->getPrefixedText();
3087 Xml::encodeJsCall( 'mw.log.error', [
3088 "Cannot preview $titleFormatted due to script-closing tag."
3089 ] )
3090 );
3091 }
3092 return $content;
3093 }
3094 }
3095 return null;
3096 } );
3097 }
3098 }
3099 return $this->rlClientContext;
3100 }
3101
3113 public function getRlClient() {
3114 if ( !$this->rlClient ) {
3115 $context = $this->getRlClientContext();
3116 $rl = $this->getResourceLoader();
3117 $this->addModules( [
3118 'user',
3119 'user.options',
3120 'user.tokens',
3121 ] );
3122 $this->addModuleStyles( [
3123 'site.styles',
3124 'noscript',
3125 'user.styles',
3126 ] );
3127 $this->getSkin()->setupSkinUserCss( $this );
3128
3129 // Prepare exempt modules for buildExemptModules()
3130 $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
3131 $exemptStates = [];
3132 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
3133
3134 // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
3135 // Separate user-specific batch for improved cache-hit ratio.
3136 $userBatch = [ 'user.styles', 'user' ];
3137 $siteBatch = array_diff( $moduleStyles, $userBatch );
3138 $dbr = wfGetDB( DB_REPLICA );
3139 ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $siteBatch );
3140 ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $userBatch );
3141
3142 // Filter out modules handled by buildExemptModules()
3143 $moduleStyles = array_filter( $moduleStyles,
3144 function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3145 $module = $rl->getModule( $name );
3146 if ( $module ) {
3147 $group = $module->getGroup();
3148 if ( isset( $exemptGroups[$group] ) ) {
3149 $exemptStates[$name] = 'ready';
3150 if ( !$module->isKnownEmpty( $context ) ) {
3151 // E.g. Don't output empty <styles>
3152 $exemptGroups[$group][] = $name;
3153 }
3154 return false;
3155 }
3156 }
3157 return true;
3158 }
3159 );
3160 $this->rlExemptStyleModules = $exemptGroups;
3161
3163 'target' => $this->getTarget(),
3164 'nonce' => $this->getCSPNonce(),
3165 // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3166 // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3167 // modules enqueud for loading on this page is filtered to just those.
3168 // However, to make sure we also apply the restriction to dynamic dependencies and
3169 // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3170 // StartupModule so that the client-side registry will not contain any restricted
3171 // modules either. (T152169, T185303)
3174 ) ? '1' : null,
3175 ] );
3176 $rlClient->setConfig( $this->getJSVars() );
3177 $rlClient->setModules( $this->getModules( /*filter*/ true ) );
3178 $rlClient->setModuleStyles( $moduleStyles );
3179 $rlClient->setExemptStates( $exemptStates );
3180 $this->rlClient = $rlClient;
3181 }
3182 return $this->rlClient;
3183 }
3184
3190 public function headElement( Skin $sk, $includeStyle = true ) {
3191 $userdir = $this->getLanguage()->getDir();
3192 $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
3193
3194 $pieces = [];
3195 $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes(
3196 $this->getRlClient()->getDocumentAttributes(),
3198 ) );
3199 $pieces[] = Html::openElement( 'head' );
3200
3201 if ( $this->getHTMLTitle() == '' ) {
3202 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3203 }
3204
3205 if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
3206 // Add <meta charset="UTF-8">
3207 // This should be before <title> since it defines the charset used by
3208 // text including the text inside <title>.
3209 // The spec recommends defining XHTML5's charset using the XML declaration
3210 // instead of meta.
3211 // Our XML declaration is output by Html::htmlHeader.
3212 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3213 // https://html.spec.whatwg.org/multipage/semantics.html#charset
3214 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3215 }
3216
3217 $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
3218 $pieces[] = $this->getRlClient()->getHeadHtml();
3219 $pieces[] = $this->buildExemptModules();
3220 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3221 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3222
3223 // Use an IE conditional comment to serve the script only to old IE
3224 $pieces[] = '<!--[if lt IE 9]>' .
3227 $this->getResourceLoader(),
3228 new FauxRequest( [] )
3229 ),
3230 [ 'html5shiv' ],
3232 [ 'sync' => true ],
3233 $this->getCSPNonce()
3234 ) .
3235 '<![endif]-->';
3236
3237 $pieces[] = Html::closeElement( 'head' );
3238
3239 $bodyClasses = $this->mAdditionalBodyClasses;
3240 $bodyClasses[] = 'mediawiki';
3241
3242 # Classes for LTR/RTL directionality support
3243 $bodyClasses[] = $userdir;
3244 $bodyClasses[] = "sitedir-$sitedir";
3245
3246 $underline = $this->getUser()->getOption( 'underline' );
3247 if ( $underline < 2 ) {
3248 // The following classes can be used here:
3249 // * mw-underline-always
3250 // * mw-underline-never
3251 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3252 }
3253
3254 if ( $this->getLanguage()->capitalizeAllNouns() ) {
3255 # A <body> class is probably not the best way to do this . . .
3256 $bodyClasses[] = 'capitalize-all-nouns';
3257 }
3258
3259 // Parser feature migration class
3260 // The idea is that this will eventually be removed, after the wikitext
3261 // which requires it is cleaned up.
3262 $bodyClasses[] = 'mw-hide-empty-elt';
3263
3264 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3265 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3266 $bodyClasses[] =
3267 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3268
3269 $bodyAttrs = [];
3270 // While the implode() is not strictly needed, it's used for backwards compatibility
3271 // (this used to be built as a string and hooks likely still expect that).
3272 $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3273
3274 // Allow skins and extensions to add body attributes they need
3275 $sk->addToBodyAttributes( $this, $bodyAttrs );
3276 Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
3277
3278 $pieces[] = Html::openElement( 'body', $bodyAttrs );
3279
3280 return self::combineWrappedStrings( $pieces );
3281 }
3282
3288 public function getResourceLoader() {
3289 if ( is_null( $this->mResourceLoader ) ) {
3290 // Lazy-initialise as needed
3291 $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3292 }
3293 return $this->mResourceLoader;
3294 }
3295
3304 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3305 // Apply 'target' and 'origin' filters
3306 $modules = $this->filterModules( (array)$modules, null, $only );
3307
3309 $this->getRlClientContext(),
3310 $modules,
3311 $only,
3312 $extraQuery,
3313 $this->getCSPNonce()
3314 );
3315 }
3316
3323 protected static function combineWrappedStrings( array $chunks ) {
3324 // Filter out empty values
3325 $chunks = array_filter( $chunks, 'strlen' );
3326 return WrappedString::join( "\n", $chunks );
3327 }
3328
3335 public function getBottomScripts() {
3336 $chunks = [];
3337 $chunks[] = $this->getRlClient()->getBodyHtml();
3338
3339 // Legacy non-ResourceLoader scripts
3340 $chunks[] = $this->mScripts;
3341
3342 if ( $this->limitReportJSData ) {
3343 $chunks[] = ResourceLoader::makeInlineScript(
3344 ResourceLoader::makeConfigSetScript(
3345 [ 'wgPageParseReport' => $this->limitReportJSData ]
3346 ),
3347 $this->getCSPNonce()
3348 );
3349 }
3350
3351 return self::combineWrappedStrings( $chunks );
3352 }
3353
3360 public function getJsConfigVars() {
3361 return $this->mJsConfigVars;
3362 }
3363
3370 public function addJsConfigVars( $keys, $value = null ) {
3371 if ( is_array( $keys ) ) {
3372 foreach ( $keys as $key => $value ) {
3373 $this->mJsConfigVars[$key] = $value;
3374 }
3375 return;
3376 }
3377
3378 $this->mJsConfigVars[$keys] = $value;
3379 }
3380
3390 public function getJSVars() {
3391 $curRevisionId = 0;
3392 $articleId = 0;
3393 $canonicalSpecialPageName = false; # T23115
3394 $services = MediaWikiServices::getInstance();
3395
3396 $title = $this->getTitle();
3397 $ns = $title->getNamespace();
3398 $canonicalNamespace = MWNamespace::exists( $ns )
3399 ? MWNamespace::getCanonicalName( $ns )
3400 : $title->getNsText();
3401
3402 $sk = $this->getSkin();
3403 // Get the relevant title so that AJAX features can use the correct page name
3404 // when making API requests from certain special pages (T36972).
3405 $relevantTitle = $sk->getRelevantTitle();
3406 $relevantUser = $sk->getRelevantUser();
3407
3408 if ( $ns == NS_SPECIAL ) {
3409 list( $canonicalSpecialPageName, /*...*/ ) =
3410 $services->getSpecialPageFactory()->
3411 resolveAlias( $title->getDBkey() );
3412 } elseif ( $this->canUseWikiPage() ) {
3413 $wikiPage = $this->getWikiPage();
3414 $curRevisionId = $wikiPage->getLatest();
3415 $articleId = $wikiPage->getId();
3416 }
3417
3418 $lang = $title->getPageViewLanguage();
3419
3420 // Pre-process information
3421 $separatorTransTable = $lang->separatorTransformTable();
3422 $separatorTransTable = $separatorTransTable ?: [];
3423 $compactSeparatorTransTable = [
3424 implode( "\t", array_keys( $separatorTransTable ) ),
3425 implode( "\t", $separatorTransTable ),
3426 ];
3427 $digitTransTable = $lang->digitTransformTable();
3428 $digitTransTable = $digitTransTable ?: [];
3429 $compactDigitTransTable = [
3430 implode( "\t", array_keys( $digitTransTable ) ),
3431 implode( "\t", $digitTransTable ),
3432 ];
3433
3434 $user = $this->getUser();
3435
3436 $vars = [
3437 'wgCanonicalNamespace' => $canonicalNamespace,
3438 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3439 'wgNamespaceNumber' => $title->getNamespace(),
3440 'wgPageName' => $title->getPrefixedDBkey(),
3441 'wgTitle' => $title->getText(),
3442 'wgCurRevisionId' => $curRevisionId,
3443 'wgRevisionId' => (int)$this->getRevisionId(),
3444 'wgArticleId' => $articleId,
3445 'wgIsArticle' => $this->isArticle(),
3446 'wgIsRedirect' => $title->isRedirect(),
3447 'wgAction' => Action::getActionName( $this->getContext() ),
3448 'wgUserName' => $user->isAnon() ? null : $user->getName(),
3449 'wgUserGroups' => $user->getEffectiveGroups(),
3450 'wgCategories' => $this->getCategories(),
3451 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3452 'wgPageContentLanguage' => $lang->getCode(),
3453 'wgPageContentModel' => $title->getContentModel(),
3454 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3455 'wgDigitTransformTable' => $compactDigitTransTable,
3456 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3457 'wgMonthNames' => $lang->getMonthNamesArray(),
3458 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3459 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3460 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3461 'wgRequestId' => WebRequest::getRequestId(),
3462 'wgCSPNonce' => $this->getCSPNonce(),
3463 ];
3464
3465 if ( $user->isLoggedIn() ) {
3466 $vars['wgUserId'] = $user->getId();
3467 $vars['wgUserEditCount'] = $user->getEditCount();
3468 $userReg = $user->getRegistration();
3469 $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3470 // Get the revision ID of the oldest new message on the user's talk
3471 // page. This can be used for constructing new message alerts on
3472 // the client side.
3473 $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3474 }
3475
3476 $contLang = $services->getContentLanguage();
3477 if ( $contLang->hasVariants() ) {
3478 $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3479 }
3480 // Same test as SkinTemplate
3481 $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3482 && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3483
3484 $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
3485 && $relevantTitle->quickUserCan( 'edit', $user )
3486 && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
3487
3488 foreach ( $title->getRestrictionTypes() as $type ) {
3489 // Following keys are set in $vars:
3490 // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3491 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3492 }
3493
3494 if ( $title->isMainPage() ) {
3495 $vars['wgIsMainPage'] = true;
3496 }
3497
3498 if ( $this->mRedirectedFrom ) {
3499 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3500 }
3501
3502 if ( $relevantUser ) {
3503 $vars['wgRelevantUserName'] = $relevantUser->getName();
3504 }
3505
3506 // Allow extensions to add their custom variables to the mw.config map.
3507 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3508 // page-dependant but site-wide (without state).
3509 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3510 Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3511
3512 // Merge in variables from addJsConfigVars last
3513 return array_merge( $vars, $this->getJsConfigVars() );
3514 }
3515
3525 public function userCanPreview() {
3526 $request = $this->getRequest();
3527 if (
3528 $request->getVal( 'action' ) !== 'submit' ||
3529 !$request->wasPosted()
3530 ) {
3531 return false;
3532 }
3533
3534 $user = $this->getUser();
3535
3536 if ( !$user->isLoggedIn() ) {
3537 // Anons have predictable edit tokens
3538 return false;
3539 }
3540 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3541 return false;
3542 }
3543
3544 $title = $this->getTitle();
3545 $errors = $title->getUserPermissionsErrors( 'edit', $user );
3546 if ( count( $errors ) !== 0 ) {
3547 return false;
3548 }
3549
3550 return true;
3551 }
3552
3556 public function getHeadLinksArray() {
3557 global $wgVersion;
3558
3559 $tags = [];
3560 $config = $this->getConfig();
3561
3562 $canonicalUrl = $this->mCanonicalUrl;
3563
3564 $tags['meta-generator'] = Html::element( 'meta', [
3565 'name' => 'generator',
3566 'content' => "MediaWiki $wgVersion",
3567 ] );
3568
3569 if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3570 // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3571 // fallbacks should come before the primary value so we need to reverse the array.
3572 foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3573 $tags["meta-referrer-$i"] = Html::element( 'meta', [
3574 'name' => 'referrer',
3575 'content' => $policy,
3576 ] );
3577 }
3578 }
3579
3580 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3581 if ( $p !== 'index,follow' ) {
3582 // http://www.robotstxt.org/wc/meta-user.html
3583 // Only show if it's different from the default robots policy
3584 $tags['meta-robots'] = Html::element( 'meta', [
3585 'name' => 'robots',
3586 'content' => $p,
3587 ] );
3588 }
3589
3590 foreach ( $this->mMetatags as $tag ) {
3591 if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3592 $a = 'http-equiv';
3593 $tag[0] = substr( $tag[0], 5 );
3594 } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3595 $a = 'property';
3596 } else {
3597 $a = 'name';
3598 }
3599 $tagName = "meta-{$tag[0]}";
3600 if ( isset( $tags[$tagName] ) ) {
3601 $tagName .= $tag[1];
3602 }
3603 $tags[$tagName] = Html::element( 'meta',
3604 [
3605 $a => $tag[0],
3606 'content' => $tag[1]
3607 ]
3608 );
3609 }
3610
3611 foreach ( $this->mLinktags as $tag ) {
3612 $tags[] = Html::element( 'link', $tag );
3613 }
3614
3615 # Universal edit button
3616 if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3617 $user = $this->getUser();
3618 if ( $this->getTitle()->quickUserCan( 'edit', $user )
3619 && ( $this->getTitle()->exists() ||
3620 $this->getTitle()->quickUserCan( 'create', $user ) )
3621 ) {
3622 // Original UniversalEditButton
3623 $msg = $this->msg( 'edit' )->text();
3624 $tags['universal-edit-button'] = Html::element( 'link', [
3625 'rel' => 'alternate',
3626 'type' => 'application/x-wiki',
3627 'title' => $msg,
3628 'href' => $this->getTitle()->getEditURL(),
3629 ] );
3630 // Alternate edit link
3631 $tags['alternative-edit'] = Html::element( 'link', [
3632 'rel' => 'edit',
3633 'title' => $msg,
3634 'href' => $this->getTitle()->getEditURL(),
3635 ] );
3636 }
3637 }
3638
3639 # Generally the order of the favicon and apple-touch-icon links
3640 # should not matter, but Konqueror (3.5.9 at least) incorrectly
3641 # uses whichever one appears later in the HTML source. Make sure
3642 # apple-touch-icon is specified first to avoid this.
3643 if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3644 $tags['apple-touch-icon'] = Html::element( 'link', [
3645 'rel' => 'apple-touch-icon',
3646 'href' => $config->get( 'AppleTouchIcon' )
3647 ] );
3648 }
3649
3650 if ( $config->get( 'Favicon' ) !== false ) {
3651 $tags['favicon'] = Html::element( 'link', [
3652 'rel' => 'shortcut icon',
3653 'href' => $config->get( 'Favicon' )
3654 ] );
3655 }
3656
3657 # OpenSearch description link
3658 $tags['opensearch'] = Html::element( 'link', [
3659 'rel' => 'search',
3660 'type' => 'application/opensearchdescription+xml',
3661 'href' => wfScript( 'opensearch_desc' ),
3662 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3663 ] );
3664
3665 # Real Simple Discovery link, provides auto-discovery information
3666 # for the MediaWiki API (and potentially additional custom API
3667 # support such as WordPress or Twitter-compatible APIs for a
3668 # blogging extension, etc)
3669 $tags['rsd'] = Html::element( 'link', [
3670 'rel' => 'EditURI',
3671 'type' => 'application/rsd+xml',
3672 // Output a protocol-relative URL here if $wgServer is protocol-relative.
3673 // Whether RSD accepts relative or protocol-relative URLs is completely
3674 // undocumented, though.
3675 'href' => wfExpandUrl( wfAppendQuery(
3676 wfScript( 'api' ),
3677 [ 'action' => 'rsd' ] ),
3679 ),
3680 ] );
3681
3682 # Language variants
3683 if ( !$config->get( 'DisableLangConversion' ) ) {
3684 $lang = $this->getTitle()->getPageLanguage();
3685 if ( $lang->hasVariants() ) {
3686 $variants = $lang->getVariants();
3687 foreach ( $variants as $variant ) {
3688 $tags["variant-$variant"] = Html::element( 'link', [
3689 'rel' => 'alternate',
3690 'hreflang' => LanguageCode::bcp47( $variant ),
3691 'href' => $this->getTitle()->getLocalURL(
3692 [ 'variant' => $variant ] )
3693 ]
3694 );
3695 }
3696 # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3697 $tags["variant-x-default"] = Html::element( 'link', [
3698 'rel' => 'alternate',
3699 'hreflang' => 'x-default',
3700 'href' => $this->getTitle()->getLocalURL() ] );
3701 }
3702 }
3703
3704 # Copyright
3705 if ( $this->copyrightUrl !== null ) {
3706 $copyright = $this->copyrightUrl;
3707 } else {
3708 $copyright = '';
3709 if ( $config->get( 'RightsPage' ) ) {
3710 $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3711
3712 if ( $copy ) {
3713 $copyright = $copy->getLocalURL();
3714 }
3715 }
3716
3717 if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3718 $copyright = $config->get( 'RightsUrl' );
3719 }
3720 }
3721
3722 if ( $copyright ) {
3723 $tags['copyright'] = Html::element( 'link', [
3724 'rel' => 'license',
3725 'href' => $copyright ]
3726 );
3727 }
3728
3729 # Feeds
3730 if ( $config->get( 'Feed' ) ) {
3731 $feedLinks = [];
3732
3733 foreach ( $this->getSyndicationLinks() as $format => $link ) {
3734 # Use the page name for the title. In principle, this could
3735 # lead to issues with having the same name for different feeds
3736 # corresponding to the same page, but we can't avoid that at
3737 # this low a level.
3738
3739 $feedLinks[] = $this->feedLink(
3740 $format,
3741 $link,
3742 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3743 $this->msg(
3744 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3745 )->text()
3746 );
3747 }
3748
3749 # Recent changes feed should appear on every page (except recentchanges,
3750 # that would be redundant). Put it after the per-page feed to avoid
3751 # changing existing behavior. It's still available, probably via a
3752 # menu in your browser. Some sites might have a different feed they'd
3753 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3754 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3755 # If so, use it instead.
3756 $sitename = $config->get( 'Sitename' );
3757 $overrideSiteFeed = $config->get( 'OverrideSiteFeed' );
3758 if ( $overrideSiteFeed ) {
3759 foreach ( $overrideSiteFeed as $type => $feedUrl ) {
3760 // Note, this->feedLink escapes the url.
3761 $feedLinks[] = $this->feedLink(
3762 $type,
3763 $feedUrl,
3764 $this->msg( "site-{$type}-feed", $sitename )->text()
3765 );
3766 }
3767 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3768 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3769 foreach ( $this->getAdvertisedFeedTypes() as $format ) {
3770 $feedLinks[] = $this->feedLink(
3771 $format,
3772 $rctitle->getLocalURL( [ 'feed' => $format ] ),
3773 # For grep: 'site-rss-feed', 'site-atom-feed'
3774 $this->msg( "site-{$format}-feed", $sitename )->text()
3775 );
3776 }
3777 }
3778
3779 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3780 # manipulating or removing existing feed tags. If you want to add new feeds, you should
3781 # use OutputPage::addFeedLink() instead.
3782 Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3783
3784 $tags += $feedLinks;
3785 }
3786
3787 # Canonical URL
3788 if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3789 if ( $canonicalUrl !== false ) {
3790 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3791 } elseif ( $this->isArticleRelated() ) {
3792 // This affects all requests where "setArticleRelated" is true. This is
3793 // typically all requests that show content (query title, curid, oldid, diff),
3794 // and all wikipage actions (edit, delete, purge, info, history etc.).
3795 // It does not apply to File pages and Special pages.
3796 // 'history' and 'info' actions address page metadata rather than the page
3797 // content itself, so they may not be canonicalized to the view page url.
3798 // TODO: this ought to be better encapsulated in the Action class.
3799 $action = Action::getActionName( $this->getContext() );
3800 if ( in_array( $action, [ 'history', 'info' ] ) ) {
3801 $query = "action={$action}";
3802 } else {
3803 $query = '';
3804 }
3805 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3806 } else {
3807 $reqUrl = $this->getRequest()->getRequestURL();
3808 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3809 }
3810 }
3811 if ( $canonicalUrl !== false ) {
3812 $tags[] = Html::element( 'link', [
3813 'rel' => 'canonical',
3814 'href' => $canonicalUrl
3815 ] );
3816 }
3817
3818 // Allow extensions to add, remove and/or otherwise manipulate these links
3819 // If you want only to *add* <head> links, please use the addHeadItem()
3820 // (or addHeadItems() for multiple items) method instead.
3821 // This hook is provided as a last resort for extensions to modify these
3822 // links before the output is sent to client.
3823 Hooks::run( 'OutputPageAfterGetHeadLinksArray', [ &$tags, $this ] );
3824
3825 return $tags;
3826 }
3827
3836 private function feedLink( $type, $url, $text ) {
3837 return Html::element( 'link', [
3838 'rel' => 'alternate',
3839 'type' => "application/$type+xml",
3840 'title' => $text,
3841 'href' => $url ]
3842 );
3843 }
3844
3854 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3855 $options = [];
3856 if ( $media ) {
3857 $options['media'] = $media;
3858 }
3859 if ( $condition ) {
3860 $options['condition'] = $condition;
3861 }
3862 if ( $dir ) {
3863 $options['dir'] = $dir;
3864 }
3865 $this->styles[$style] = $options;
3866 }
3867
3875 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3876 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3877 # If wanted, and the interface is right-to-left, flip the CSS
3878 $style_css = CSSJanus::transform( $style_css, true, false );
3879 }
3880 $this->mInlineStyles .= Html::inlineStyle( $style_css );
3881 }
3882
3888 protected function buildExemptModules() {
3889 $chunks = [];
3890 // Things that go after the ResourceLoaderDynamicStyles marker
3891 $append = [];
3892
3893 // We want site, private and user styles to override dynamically added styles from
3894 // general modules, but we want dynamically added styles to override statically added
3895 // style modules. So the order has to be:
3896 // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
3897 // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
3898 // - ResourceLoaderDynamicStyles marker
3899 // - site/private/user styles
3900
3901 // Add legacy styles added through addStyle()/addInlineStyle() here
3902 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3903
3904 $chunks[] = Html::element(
3905 'meta',
3906 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3907 );
3908
3909 $separateReq = [ 'site.styles', 'user.styles' ];
3910 foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3911 // Combinable modules
3912 $chunks[] = $this->makeResourceLoaderLink(
3913 array_diff( $moduleNames, $separateReq ),
3915 );
3916
3917 foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3918 // These require their own dedicated request in order to support "@import"
3919 // syntax, which is incompatible with concatenation. (T147667, T37562)
3920 $chunks[] = $this->makeResourceLoaderLink( $name,
3922 );
3923 }
3924 }
3925
3926 return self::combineWrappedStrings( array_merge( $chunks, $append ) );
3927 }
3928
3932 public function buildCssLinksArray() {
3933 $links = [];
3934
3935 foreach ( $this->styles as $file => $options ) {
3936 $link = $this->styleLink( $file, $options );
3937 if ( $link ) {
3938 $links[$file] = $link;
3939 }
3940 }
3941 return $links;
3942 }
3943
3951 protected function styleLink( $style, array $options ) {
3952 if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
3953 return '';
3954 }
3955
3956 if ( isset( $options['media'] ) ) {
3957 $media = self::transformCssMedia( $options['media'] );
3958 if ( is_null( $media ) ) {
3959 return '';
3960 }
3961 } else {
3962 $media = 'all';
3963 }
3964
3965 if ( substr( $style, 0, 1 ) == '/' ||
3966 substr( $style, 0, 5 ) == 'http:' ||
3967 substr( $style, 0, 6 ) == 'https:' ) {
3968 $url = $style;
3969 } else {
3970 $config = $this->getConfig();
3971 // Append file hash as query parameter
3972 $url = self::transformResourcePath(
3973 $config,
3974 $config->get( 'StylePath' ) . '/' . $style
3975 );
3976 }
3977
3978 $link = Html::linkedStyle( $url, $media );
3979
3980 if ( isset( $options['condition'] ) ) {
3981 $condition = htmlspecialchars( $options['condition'] );
3982 $link = "<!--[if $condition]>$link<![endif]-->";
3983 }
3984 return $link;
3985 }
3986
4008 public static function transformResourcePath( Config $config, $path ) {
4009 global $IP;
4010
4011 $localDir = $IP;
4012 $remotePathPrefix = $config->get( 'ResourceBasePath' );
4013 if ( $remotePathPrefix === '' ) {
4014 // The configured base path is required to be empty string for
4015 // wikis in the domain root
4016 $remotePath = '/';
4017 } else {
4018 $remotePath = $remotePathPrefix;
4019 }
4020 if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
4021 // - Path is outside wgResourceBasePath, ignore.
4022 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
4023 return $path;
4024 }
4025 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
4026 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
4027 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
4028 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
4029 $uploadPath = $config->get( 'UploadPath' );
4030 if ( strpos( $path, $uploadPath ) === 0 ) {
4031 $localDir = $config->get( 'UploadDirectory' );
4032 $remotePathPrefix = $remotePath = $uploadPath;
4033 }
4034
4035 $path = RelPath::getRelativePath( $path, $remotePath );
4036 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
4037 }
4038
4050 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
4051 $hash = md5_file( "$localPath/$file" );
4052 if ( $hash === false ) {
4053 wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
4054 $hash = '';
4055 }
4056 return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
4057 }
4058
4066 public static function transformCssMedia( $media ) {
4067 global $wgRequest;
4068
4069 // https://www.w3.org/TR/css3-mediaqueries/#syntax
4070 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
4071
4072 // Switch in on-screen display for media testing
4073 $switches = [
4074 'printable' => 'print',
4075 'handheld' => 'handheld',
4076 ];
4077 foreach ( $switches as $switch => $targetMedia ) {
4078 if ( $wgRequest->getBool( $switch ) ) {
4079 if ( $media == $targetMedia ) {
4080 $media = '';
4081 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
4082 /* This regex will not attempt to understand a comma-separated media_query_list
4083 *
4084 * Example supported values for $media:
4085 * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
4086 * Example NOT supported value for $media:
4087 * '3d-glasses, screen, print and resolution > 90dpi'
4088 *
4089 * If it's a print request, we never want any kind of screen stylesheets
4090 * If it's a handheld request (currently the only other choice with a switch),
4091 * we don't want simple 'screen' but we might want screen queries that
4092 * have a max-width or something, so we'll pass all others on and let the
4093 * client do the query.
4094 */
4095 if ( $targetMedia == 'print' || $media == 'screen' ) {
4096 return null;
4097 }
4098 }
4099 }
4100 }
4101
4102 return $media;
4103 }
4104
4111 public function addWikiMsg( /*...*/ ) {
4112 $args = func_get_args();
4113 $name = array_shift( $args );
4114 $this->addWikiMsgArray( $name, $args );
4115 }
4116
4125 public function addWikiMsgArray( $name, $args ) {
4126 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
4127 }
4128
4154 public function wrapWikiMsg( $wrap /*, ...*/ ) {
4155 $msgSpecs = func_get_args();
4156 array_shift( $msgSpecs );
4157 $msgSpecs = array_values( $msgSpecs );
4158 $s = $wrap;
4159 foreach ( $msgSpecs as $n => $spec ) {
4160 if ( is_array( $spec ) ) {
4161 $args = $spec;
4162 $name = array_shift( $args );
4163 if ( isset( $args['options'] ) ) {
4164 unset( $args['options'] );
4166 'Adding "options" to ' . __METHOD__ . ' is no longer supported',
4167 '1.20'
4168 );
4169 }
4170 } else {
4171 $args = [];
4172 $name = $spec;
4173 }
4174 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4175 }
4176 $this->addWikiTextAsInterface( $s );
4177 }
4178
4184 public function isTOCEnabled() {
4185 return $this->mEnableTOC;
4186 }
4187
4194 public function enableSectionEditLinks( $flag = true ) {
4195 wfDeprecated( __METHOD__, '1.31' );
4196 }
4197
4203 public function sectionEditLinksEnabled() {
4204 wfDeprecated( __METHOD__, '1.31' );
4205 return true;
4206 }
4207
4215 public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4216 $themes = ResourceLoaderOOUIModule::getSkinThemeMap();
4217 $theme = $themes[$skinName] ?? $themes['default'];
4218 // For example, 'OOUI\WikimediaUITheme'.
4219 $themeClass = "OOUI\\{$theme}Theme";
4220 OOUI\Theme::setSingleton( new $themeClass() );
4221 OOUI\Element::setDefaultDir( $dir );
4222 }
4223
4230 public function enableOOUI() {
4231 self::setupOOUI(
4232 strtolower( $this->getSkin()->getSkinName() ),
4233 $this->getLanguage()->getDir()
4234 );
4235 $this->addModuleStyles( [
4236 'oojs-ui-core.styles',
4237 'oojs-ui.styles.indicators',
4238 'oojs-ui.styles.textures',
4239 'mediawiki.widgets.styles',
4240 'oojs-ui.styles.icons-content',
4241 'oojs-ui.styles.icons-alerts',
4242 'oojs-ui.styles.icons-interactions',
4243 ] );
4244 }
4245
4255 public function getCSPNonce() {
4257 return false;
4258 }
4259 if ( $this->CSPNonce === null ) {
4260 // XXX It might be expensive to generate randomness
4261 // on every request, on Windows.
4262 $rand = random_bytes( 15 );
4263 $this->CSPNonce = base64_encode( $rand );
4264 }
4265 return $this->CSPNonce;
4266 }
4267
4268}
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
$wgVersion
MediaWiki version number.
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.
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...
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
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.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
getContext()
$wgParser
Definition Setup.php:886
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:728
$IP
Definition WebStart.php:41
if( $line===false) $args
Definition cdb.php:64
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition Article.php:1027
static isNonceRequired(Config $config)
Should we set nonce attribute.
static sendHeaders(IContextSource $context)
Send CSP headers based on wiki config.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
getWikiPage()
Get the WikiPage object.
setContext(IContextSource $context)
Allows changing specific properties of a context object, without changing the main one.
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:52
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:28
Content for JavaScript pages.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:34
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 provides methods which fulfil two basic services:
Definition Message.php:160
This class should be covered by a general architecture document which does not exist as of January 20...
isArticle()
Return whether the content displayed page is related to the source of the corresponding article on th...
addWikiMsg()
Add a wikitext-formatted message to the output.
addLinkHeader( $header)
Add an HTTP Link: header.
setFileVersion( $file)
Set the displayed file version.
$mScripts
Used for JavaScript (predates ResourceLoader)
bool $mCanonicalUrl
getDisplayTitle()
Returns page display title.
string $CSPNonce
The nonce for Content-Security-Policy.
getCdnCacheEpoch( $reqTime, $maxAge)
disable()
Disable output completely, i.e.
addLink(array $linkarr)
Add a new <link> tag to the page header.
ResourceLoader $mResourceLoader
showFileCopyError( $old, $new)
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.
addWikiText( $text, $linestart=true, $interface=true)
Convert wikitext to HTML and add it to the buffer Default assumes that the current page title will be...
array $mModules
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:".
showFileNotFoundError( $name)
parseInline( $text, $linestart=true, $interface=false)
Parse wikitext, strip paragraph wrapper, and return the HTML.
showFileRenameError( $old, $new)
$mFeedLinks
Handles the Atom / RSS links.
bool $mNewSectionLink
showUnexpectedValueError( $name, $val)
setCopyrightUrl( $url)
Set the copyright URL to send with the output.
parseInternal( $text, $title, $linestart, $tidy, $interface, $language)
Parse wikitext and return the HTML (internal implementation helper)
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.
addWikiTextTitleTidy( $text, Title $title, $linestart=true)
Add wikitext in content language with a custom Title object.
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] in 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.
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.
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.
addWikiTextWithTitle( $text, Title $title, $linestart=true)
Add wikitext with a custom Title object.
addContentOverride(LinkTarget $target, Content $content)
Add a mapping from a LinkTarget to a Content, for things like page preview.
array $mMetatags
Should be private.
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.
addWikiTextTitle( $text, Title $title, $linestart, $tidy=false, $interface=false)
Add wikitext with a custom Title object.
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.
addModuleStyles( $modules)
Load the styles of one or more 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.
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...
getCategories( $type='all')
Get the list of category names this page belongs to.
sectionEditLinksEnabled()
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.
getFileSearchOptions()
Get the files used on this page.
addWikiTextTidy( $text, $linestart=true)
Add wikitext in content language.
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.
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.
enableSectionEditLinks( $flag=true)
Enables/disables section edit links, doesn't override NOEDITSECTION
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.
addWikiTextTitleInternal( $text, Title $title, $linestart, $tidy, $interface, $wrapperClass=null)
Add wikitext with a custom Title object.
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,...
showFileDeleteError( $name)
addInlineScript( $script)
Add a self-contained script tag with the given contents Internal use only.
addAcceptLanguage()
T23672: Add Accept-Language to Vary and Key headers if there's no 'variant' parameter in GET.
setStatusCode( $statusCode)
Set the HTTP status code to send with the output.
getCacheVaryCookies()
Get the list of cookie names that will influence the cache.
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...
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.
parserOptions( $options=null)
Get/set the ParserOptions object to use for wikitext parsing.
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.
wrapWikiMsg( $wrap)
This function takes a number of message/argument specifications, wraps them in some overall structure...
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.
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.
bool $mArticleBodyOnly
Flag if output should only contain the body of the article.
setSubtitle( $str)
Replace the subtitle with $str.
array $rlExemptStyleModules
getKeyHeader()
Get a complete Key header.
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
parse( $text, $linestart=true, $interface=false, $language=null)
Parse wikitext and return the HTML.
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.
getText( $options=[])
Get the output HTML.
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)
Explicily 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.
Object passed around to modules which contains information about the state of a specific loader reque...
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
getOrigin()
Get this module's origin.
Dynamic JavaScript and CSS resource loading system.
The main skin class which provides methods and properties for all other skins.
Definition Skin.php:38
getHtmlElementAttributes()
Return values for <html> element.
Definition Skin.php:473
getSkinName()
Definition Skin.php:155
addToBodyAttributes( $out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag,...
Definition Skin.php:489
getPageClasses( $title)
TODO: document.
Definition Skin.php:437
Represents a title within MediaWiki.
Definition Title.php:40
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition User.php:5062
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:64
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
const PROTO_CANONICAL
Definition Defines.php:232
const PROTO_CURRENT
Definition Defines.php:231
const NS_SPECIAL
Definition Defines.php:62
const PROTO_RELATIVE
Definition Defines.php:230
const NS_CATEGORY
Definition Defines.php:87
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2228
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2843
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
Definition hooks.txt:822
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:181
either a plain
Definition hooks.txt:2054
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:1999
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition hooks.txt:2848
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:955
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition hooks.txt:783
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition hooks.txt:856
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition hooks.txt:2290
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition hooks.txt:2011
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3069
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:271
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:2012
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext such as when responding to a resource loader request or generating HTML output & $resourceLoader
Definition hooks.txt:2859
this hook is for auditing only $response
Definition hooks.txt:780
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1617
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form before processing starts Return false to skip default processing and return $ret $linkRenderer
Definition hooks.txt:2054
return true to allow those checks to and false if checking is done & $user
Definition hooks.txt:1510
passed in as a query string parameter to the various URLs constructed here(i.e. $prevlink) $ldel you ll need to handle error messages
Definition hooks.txt:1290
returning false will NOT prevent logging $e
Definition hooks.txt:2175
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for configuration instances.
Definition Config.php:28
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Base interface for content objects.
Definition Content.php:34
Interface for objects which can provide a MediaWiki context on request.
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
Result wrapper for grabbing data queried from an IDatabase object.
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$content
$filter
const DB_REPLICA
Definition defines.php:25
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition router.php:42
$params
if(!isset( $args[0])) $lang
$header